Introduction
Welcome to the Quaderno API! You can use our API to access all our features, designed around the idea of making tax management and invoicing easy for small businesses.
The Quaderno API is based on the REST architectural style, and comprised of resources with predictable, human-readable and resource-oriented URLs. We make use of standard HTTP features (HTTP Authentication, Verbs, and Response Codes), understandable by any off-the-shelf HTTP client. We return JSON for everything (including errors!), and expect to receive JSON for all POST
and PUT
requests.
We have language bindings for the Shell, Ruby, PHP and Swift! You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top-right.
If you have any suggestions, tips, or questions that you feel aren’t answered here or in our Knowledge Base, please get in touch and let us know!
Getting Started
Endpoint URL Structure
API Endpoint
https://ACCOUNT_NAME.quadernoapp.com/api/
All URLs start with the root https://ACCOUNT_NAME.quadernoapp.com/api/
.
ACCOUNT_NAME
is different for each user, and is used as the identifier for which user to affect with an API call. You can see this in the URL bar when logging into your Quaderno account.- Note that the API version is also included in the root of every call. This ensures that updates to the API will not break older code, but when you are ready to make the switch (and when we have a new version) you can do so by updating this root.
The resource in question will follow, like so:
https://ACCOUNT_NAME.quadernoapp.com/api/RESOURCE.json
Authentication
To authorize, use this code:
Quaderno::Base.configure do |config|
config.auth_token = 'YOUR_API_KEY'
config.url = 'YOUR_API_URL'
end
require_once 'quaderno_load.php';
QuadernoBase::init('YOUR_API_KEY',
'YOUR_API_URL');
let client = Quaderno.Client(baseURL: "YOUR_API_URL",
authenticationToken: "YOUR_API_KEY")
# With curl, you can pass the API key as a header with each request
curl -u YOUR_API_KEY:x \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/invoices.json'
You can
ping
to check if the service is up, your credentials are correct, or to know your remaining requests without doing an actual request:
Quaderno::Base.ping #=> Boolean
curl -u YOUR_API_KEY:x \
-X GET \
'https://quadernoapp.com/api/ping.json'
QuadernoBase::ping(); // Returns true (success) or false (error)
let client = Quaderno.Client(/* ... */)
client.ping { success in
// success will be true if the service is available.
}
Quaderno uses an API key to authorise all requests, allowing access to the API in combination with the account name in the endpoint URL. For more info on finding your API key, check here.
Quaderno expects the API key to be included via HTTP Basic Auth in all API requests to the server, in a header that looks like the following:
-u YOUR_API_KEY:x
Authorization
curl -u YOUR_API_KEY:x \
-X GET \
'https://quadernoapp.com/api/authorization.json'
Quaderno::Base.authorization 'my_authenticate_token', environment
# environment is an optional argument. By passing :sandbox,
# you will retrieve your credentials for the sandbox
# environment and not for production.
let client = Quaderno.Client(/* ... */)
client.account { credentials in
// credentials will contain the account credentials.
}
Returns:
{
"id": "999",
"name": "Sheldon Cooper",
"email": "s.cooperphd@yahoo.com",
"href": "http://nippur-999.quadernoapp.com/api/"
}
If you don’t know the ACCOUNT_NAME
for your target account, you can get it with the authorization
API call.
This returns the following as a JSON payload:
Attribute | Description |
---|---|
id |
An identity, which is not used for determining who this user is within Quaderno. The id field should therefore not be used for submitting data within Quaderno’s API. |
name |
The user’s full name. |
email |
The user’s email address. |
href |
The custom API endpoint URL for the user, providing the sought-after ACCOUNT_NAME between http:// and .quadernoapp... . |
Making a request
A basic GET for a user’s contacts looks like this:
# :x stops cURL from prompting for a password
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json'
// Returns an array of QuadernoContact
$contacts = QuadernoContact::find();
Quaderno::Contact.all() #=> Array
let client = Quaderno.Client(/* ... */)
let readContact = Contact.list(pageNum)
client.request(readContact) { response in
// response will contain the result of the request.
}
A POST with a new contact looks like this:
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
-d {"first_name":"Tony", "kind":"person", "contact_name":"Stark"}
'https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json'
$contact = new QuadernoContact(array(
'first_name' => 'Tony',
'kind' => 'person',
'contact_name' => 'Stark'));
$contact->save(); // Returns true (success) or false (error)
# Using new hash syntax
params = {
first_name: 'Tony',
kind: 'person',
contact_name: 'Stark'
}
Quaderno::Contact.create(params) #=> Quaderno::Contact
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"first_name": "Tony",
"kind": "person",
"contact_name": "Stark"
]
let createContact = Contact.create(params)
client.request(createContact) { response in
// response will contain the result of the request.
}
As mentioned, we use standard HTTP verbs for the API requests: GET
,POST
, PUT
and DELETE
.
Every request involves JSON (even in error cases), and we only support JSON for serialization of data.
Our format is to have:
- No root element
snake_case
to describe attribute keys
When sending JSON (in PUT
or POST
requests), you must specify Content-Type: application/json;
as the header of the HTTP request.
JSON & cURL
POSTing large amounts of JSON
POST /items.json
(with a large JSON body to send)
# body.json
{
"code":"BluRay 0003",
"name":"Titanic IV: The revenge",
"unit_cost":"15.0",
"tax_1_name":"AWESOME_TAX",
"tax_1_rate":"7.00",
"tax_2_name":"ANOTHER_AWESOME_TAX",
"tax_2_rate":"10.00",
"stock":"1000"
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/items.json'
Multiline cURL with JSON is a little bit tricky. In our experience the cleanest, easiest way to make it work correctly is actually to save the desired JSON payload to a file (we use body.json
as the filename in our examples) and pass it to the cURL command in the --data-binary
flag, effectively passing the file in by stdin.
Your mileage may vary, but this is the way that we recommend.
Pretty printing JSON responses
A fun bit of useful cURLing-with-JSON-related fun is to append one of the following commands to your request:
| json_pp
| python -mjson.tool > out.json
(on systems with Python installed - this will write to a file)| jq .
This will cause the JSON to “pretty print”, meaning that it will come out nicely formatted like the examples in these docs instead of as one big jumbled mess.
Different commands work better under different circumstances, so check which works best on your system. For us, on OS X 10.11, | json_pp
is the winner for quick in-terminal checks, and | python -mjson.tool > out.json
for writing results to files for later perusal.
These will also be useful with other types of HTTP requests, but most useful when getting large chunks of data, as in list
-type calls.
Rate Limiting
To make it easier to determine if your application is being rate-limited, or is approaching that level, we have the following HTTP headers on our successful responses:
X-RateLimit-Reset
X-RateLimit-Remaining
Quaderno::Base.rate_limit_info #=> {:reset=>4, :remaining=>0}
// You can check the entitlements for using the service (e.g. the rate
// limit) by inspecting the `entitlements` property of `Client`.
// See the quaderno/quaderno-swift docs for more.
There is a limit of 100 API calls per 15 seconds.
We reserve the right to tune the limitations, but we promise to keep them high enough to allow a well-behaving interactive app to do it’s job.
If you exceed the limit you will receive a HTTP 429 (Too Many Requests)
.
Pagination
These HTTP headers inform you about the page context:
X-Pages-CurrentPage
X-Pages-TotalPages
A call with the
page
parameter set:
curl -u YOUR_API_KEY:x \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json?page=2'
// Returns an array of QuadernoContact
$contacts = QuadernoContact::find(array('page' => 2));
Quaderno::Contact.all(page: 1) #=> Array
let client = Quaderno.Client(/* ... */)
let listContacts = Contact.list(pageNum)
client.request(listContacts) { response in
// response will contain the result of the request.
}
Bear in mind that Quaderno paginates GET
index results, providing 25 results per page.
You can change the page by passing the page
parameter, defaulting to 1
.
Versioning
When we make breaking changes to our API, we release a new version number which you can change in your calls.
By default if you don’t specify a version, we’ll use the one stored in your Quaderno account. To override this value you can pass the version in the Accept
HTTP header like this: Accept: application/json; api_version=API_VERSION_NUMBER
# Example of API version override
curl -u YOUR_API_KEY:x \
-H 'Accept: application/json; api_version=20160602' \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json?page=2'
Testing
We recommend testing against our Sandbox environment. You can sign up for a sandbox account (test-ready) here.
Contacts
A contact is any client, customer or vendor who appears on your invoices or expenses.
Create a contact
POST /contacts.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
-d '{"first_name":"Tony", "kind":"person", "contact_name":"Stark"}' \
'https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json'
$contact = new QuadernoContact(array(
'first_name' => 'Tony',
'kind' => 'person',
'contact_name' => 'Stark'));
$contact->save(); // Returns true (success) or false (error)
# Using new hash syntax
params = {
first_name: 'Tony',
kind: 'person',
contact_name: 'Stark'
}
Quaderno::Contact.create(params) #=> Quaderno::Contact
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"first_name": "Tony",
"kind": "person",
"contact_name": "Stark"
]
let createContact = Contact.create(params)
client.request(createContact) { response in
// response will contain the result of the request.
}
POST
ing to /contacts.json
will create a new contact from the parameters passed.
This will return 201 Created
and the current JSON representation of the contact if the creation was a success, along with the location of the new contact in the url
field.
Mandatory Fields
Key | Description |
---|---|
first_name |
The first name of the contact. |
bic |
If sending a bank_account (in electronic IBAN format). Must be 11 characters in length. |
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
kind | no | company or person . Defaults to company . |
first_name | yes | String(255 chars) |
last_name | no | String(255 chars) |
contact_name | no | String(255 chars) |
street_line_1 | no | String(255 chars) |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars) |
postal_code | no | String(255 chars) |
region | no | String(255 chars) |
country | no | String(2 chars) ISO 3166-1 alpha-2 |
secondary_street_line_1 | no | String(255 chars) |
secondary_street_line_2 | no | String(255 chars) |
secondary_postal_code | no | String(255 chars) |
secondary_city | no | String(255 chars) |
secondary_region | no | String(255 chars) |
secondary_country | no | String(255 chars) |
phone_1 | no | String(255 chars) |
phone_2 | no | String(255 chars) |
fax | no | String(255 chars) |
no | String(255 chars) Multiple emails should be separated by commas. Maximum number of addresses allowed is 3. | |
web | no | String(255 chars). Validates format |
discount | no | Decimal |
language | no | String(2 chars) Should be included in the translations list |
tax_id | no | String(255 chars) |
vat_number | no | String(255 chars) If present, it is validated against VIES |
bank_account | no | String(255 chars) format is validated |
bic | yes/no | String(255 chars) Mandatory if bank_account is present. Format is validated |
notes | no | Text |
Retrieve: Get and filter all contacts
GET /contacts.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json'
Quaderno::Contact.all() #=> Array
$contacts = QuadernoContact::find(); // Returns an array of QuadernoContact
let client = Quaderno.Client(/* ... */)
let listContacts = Contact.list(pageNum)
client.request(listContacts) { response in
// response will contain the result of the request.
}
[
{
"id":"456987213",
"kind":"person",
"first_name":"Sheldon",
"last_name":"Cooper",
"full_name":"Sheldon Cooper",
"street_line_1":"2311 N. Los Robles Avenue",
"street_line_2":"",
"postal_code":"91104",
"city":"Pasadena",
"region":"CA",
"country":"US",
"phone_1":"",
"phone_2":"",
"fax":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"secure_id":"th3p3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/contacts/456987213"
},
{
"id":"456982365",
"kind":"company",
"full_name":"Apple Inc.",
"street_line_1":"1 Infinite Loop",
"street_line_2":"",
"postal_code":"95014",
"city":"Cupertino",
"region":"CA",
"country":"US",
"phone_1":"",
"phone_2":"",
"fax":"",
"email":"info@apple.com",
"web":"http://apple.com",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"secure_id":"4n0th3rp3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/4n0th3rp3rm4l1nk",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/contacts/456982365"
}
]
GET
ting from /contacts.json
will return all the user’s contacts.
You can filter the results by full name, email or tax ID by passing the q
parameter in the URL as a query string, like ?q=KEYWORD
.
Retrieve: Get a single contact
GET /contacts/CONTACT_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/contacts/CONTACT_ID.json'
Quaderno::Contact.find(CONTACT_ID) #=> Quaderno::Contact
$contact = QuadernoContact::find(CONTACT_ID); // Returns a QuadernoContact
let client = Quaderno.Client(/* ... */)
let readContact = Contact.read(CONTACT_ID)
client.request(readContact) { response in
// response will contain the result of the request.
}
{
"id":"456987213",
"kind":"person",
"first_name":"Sheldon",
"last_name":"Cooper",
"full_name":"Sheldon Cooper",
"street_line_1":"2311 N. Los Robles Avenue",
"street_line_2":"",
"postal_code":"91104",
"city":"Pasadena",
"region":"CA",
"country":"US",
"phone_1":"",
"phone_2":"",
"fax":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"secure_id":"th3p3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/contacts/456987213"
}
GET
ting from /contacts/CONTACT_ID.json
will get you that specific contact.
Retrieve: Get a single contact by payment gateway ID
GET /PAYMENT_GATEWAY/customers/PAYMENT_GATEWAY_CUSTOMER_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/PAYMENT_GATEWAY/customers/PAYMENT_GATEWAY_CUSTOMER_ID.json'
Quaderno::Contact.retrieve_customer(PAYMENT_GATEWAY_CUSTOMER_ID, PAYMENT_GATEWAY) #=> Quaderno::Contact
{
"id":"456987213",
"kind":"person",
"first_name":"Sheldon",
"last_name":"Cooper",
"full_name":"Sheldon Cooper",
"street_line_1":"2311 N. Los Robles Avenue",
"street_line_2":"",
"postal_code":"91104",
"city":"Pasadena",
"region":"CA",
"country":"US",
"phone_1":"",
"phone_2":"",
"fax":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"secure_id":"th3p3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/contacts/456987213"
}
GET
ting from /PAYMENT_GATEWAY/customers/PAYMENT_GATEWAY_CUSTOMER_ID.json
will get you that specific contact by using their ID with the payment gateway they were created with.
Supported gateways:
More are added frequently, so check back!
Update a contact
PUT /contacts/CONTACT_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"first_name":"Anthony"}' \
'https://ACCOUNT_NAME.quadernoapp.com/api/contacts/CONTACT_ID.json'
Quaderno::Contact.update(CONTACT_ID, params) #=> Quaderno::Contact
$contact->first_name = 'Anthony';
$contact->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"first_name": "Anthony"
]
let updateContact = Contact.update(CONTACT_ID, params)
client.request(updateContact) { response in
// response will contain the result of the request.
}
PUT
ing to /contacts/CONTACT_ID.json
will update the contact from the passed parameters.
This will return 200 OK
and a JSON representation of the contact if successful.
Delete a contact
DELETE /contacts/CONTACT_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE
'https://ACCOUNT_NAME.quadernoapp.com/api/contacts/CONTACT_ID.json'
Quaderno::Contact.delete(CONTACT_ID) #=> Boolean
$contact->delete();
let client = Quaderno.Client(/* ... */)
let deleteContact = Contact.delete(CONTACT_ID)
client.request(deleteContact) { response in
// response will contain the result of the request.
}
DELETE
ing to /contacts/CONTACT_ID.json
will delete the specified contact and returns 204 No Content
if successful.
Receipts
A receipt is a detailed list of goods shipped or services rendered, with an account of all costs. It differs from invoices in a few key ways (mostly in that it does not require customer information in a legal sense). Read more here.
Create a receipt
POST /receipts.json
# body.json
{
"payment_method":"credit_card",
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"STARK",
"po_number":"",
"currency":"USD",
"tag_list":"playboy, businessman",
"items_attributes":[
{
"description":"Whiskey",
"quantity":"1.0",
"unit_price":"20.0",
"discount_rate":"0.0",
"reference":"item_code_X"
}
],
"custom_metadata":{
"a_custom_key":"a custom value"
}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/receipts.json'
$receipt = new QuadernoReceipt(array(
'contact_id' => '5059bdbf2f412e0901000024',
'payment_method' => 'credit_card',
'po_number' => '',
'currency' => 'USD',
'tag_list' => array('playboy', 'businessman'),
'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
'description' => 'Pizza bagels',
'unit_price' => 9.99,
'quantity' => 20));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');
$receipt->addItem($item);
$receipt->addContact($contact);
$receipt->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('50603e722f412e0435000024') #=> Quaderno::Contact
params = {
contact_id: contact.id,
po_number: '',
payment_method: 'credit_card',
currency: 'USD',
tag_list: ['playboy', 'businessman'],
items_attributes: [
{
description: 'Whiskey',
quantity: '1.0',
unit_price: '20.0',
discount_rate: '0.0'
}
],
custom_metadata: {
a_custom_key: 'a custom value'
}
}
receipt = Quaderno::Receipt.create(params) #=> Quaderno::Receipt
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"payment_method":"credit_card",
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"STARK",
"po_number":"",
"currency":"USD",
"tag_list":"playboy, businessman"
]
let createReceipt = Receipt.create(params)
client.request(createReceipt) { response in
// response will contain the result of the request.
}
POST
ing to /receipts.json
will create a new receipt from the parameters passed.
This will return 201 Created
and the current JSON representation of the receipt if the creation was a success, along with the location of the new receipt in the url
field.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
number | no | String(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness |
issue_date | no | String(255 chars) Available for all except for recurring . Defaults to the current date. Format YYYY-MM-DD |
po_number | no | String(255 chars) |
due_date | no | String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD |
currency | no | String(3 chars) Defaults to the account currency. En formato ISO 4217 |
tag_list | no | String(255 chars). Multiple tags should be separated by commas |
payment_details | no | Text |
notes | no | Text |
contact_id | (Mandatory if contact is not present) |
ID |
contact | (Mandatory if contact_id is not present) |
Hash with a contact data for creation |
street_line_1 | no | String(255 chars). Available for updates |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars). Available for updates |
region | no | String(255 chars). Available for updates |
postal_code | no | String(255 chars). Available for updates |
items_attributes | yes | Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items. |
payment_method | yes | One of the following: credit_card , cash , wire_transfer , direct_debit , check , promissory_note , iou , paypal or other |
custom_metadata | no | Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Document Item Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
id | no | ID. Available only for updates |
description | yes | String(255 chars) |
quantity | no | Decimal. Defaults to 1.0 |
unit_price | yes | Decimal |
total_amount | Mandatory if unit_price is not present |
Decimal |
discount_rate | no | Decimal |
tax_1_name | Mandatory if tax_1_rate is present |
String(255 chars) |
tax_1_rate | Mandatory if tax_1_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_1_country | no | String(2 chars). Defaults to the contact’s country |
tax_1_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
tax_2_name | Mandatory if tax_2_rate is present |
String(255 chars) |
tax_2_rate | Mandatory if tax_2_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_2_country | no | String(2 chars). Defaults to the contact’s country |
tax_2_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
reference | no | String(255 chars) Code (code ) of an existing item. If present none of the mandatory attributes are mandatory (as you are referencing an item that already exists) |
_destroy | no | Set it to 1 if you want to remove the document item selected by ID. Available only for updates |
Receipt States
Possible receipt states are:
paid
(default state)invoiced
(final state reached when the receipt is invoiced and cannot be undone)
Retrieve: Get and filter all receipts
GET /receipts.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/receipts.json'
Quaderno::Receipt.all() #=> Array
$receipts = QuadernoReceipt::find(); // Returns an array of QuadernoReceipt
let client = Quaderno.Client(/* ... */)
let listReceipts = Receipt.list(pageNum)
client.request(listReceipts) { response in
// response will contain the result of the request.
}
[
{
"id":"507693322f412e0e2e00000f",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"contact":{
"id":"5073f9c22f412e02d0000032",
"full_name":"Garfield"
},
"country":"US",
"street_line_1":"Street 23",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"10203",
"po_number":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"lasagna",
"quantity":"25.0",
"unit_price_cents":"375",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Awesome!",
"subtotal_cents":"9375",
"discount_cents":"0",
"gross_amount_cents":"9375"
}
],
"subtotal_cents":"9375",
"discount_cents":"0",
"taxes":[],
"total_cents":"9375",
"payments":[
{
"id":"50aca7d92f412eda5200002c",
"date":"2012-11-21",
"payment_method":"credit_card",
"amount_cents":"9375",
},
],
"notes":"",
"state":"paid",
"tag_list":["lasagna", "cat"],
"secure_id":"7hef1rs7p3rm4l1nk",
"permalink":"https://quadernoapp.com/receipt/7hef1rs7p3rm4l1nk",
"pdf":"https://quadernoapp.com/receipt/7hef1rs7p3rm4l1nk.pdf",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/receipts/507693322f412e0e2e00000f.json",
"custom_metadata":{}
},
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376020",
"contact":{
"id":"5073f9c22f412e02d00004cf",
"full_name":"Teenage Mutant Ninja Turtles"
},
"country":"US",
"street_line_1":"Melrose Ave, Sewer #3",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"",
"po_number":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"481516234291",
"description":"pizza",
"quantity":"1.0",
"unit_price_cents":"6000",
"discount_rate":"0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Even the bad ones taste good!",
"subtotal_cents":"6000",
"discount_cents":"0",
"gross_amount_cents":"6000"
}
],
"subtotal_cents":"6000",
"discount_cents":"0",
"taxes":[],
"total_cents":"6000",
"payments":[
{
"id":"50aca7d92f412eda5200002ssdc",
"date":"2012-11-21",
"payment_method":"credit_card",
"amount_cents":"6000",
},
],
"notes":"",
"state":"outstanding",
"tag_list":["pizza", "turtles"],
"secure_id":"7hes3c0ndp3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/receipt/7hes3c0ndp3rm4l1nk",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/receipt/7hes3c0ndp3rm4l1nk.pdf",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/receipts/507693322f412e0e2e0000da.json",
"custom_metadata":{}
}
]
GET
ting from /receipts.json
will return all the user’s receipts.
You can filter the results in a few ways:
- By
number
,contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By contact, passing the contact ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single receipt
GET /receipts/RECEIPT_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/receipts/RECEIPT_ID.json'
Quaderno::Receipt.find(RECEIPT_ID) #=> Quaderno::Receipt
$receipt = QuadernoReceipt::find('RECEIPT_ID'); // Returns a QuadernoReceipt
let client = Quaderno.Client(/* ... */)
let readReceipt = Receipt.read(RECEIPT_ID)
client.request(readReceipt) { response in
// response will contain the result of the request.
}
{
"id":13638223434,
"number":"0000001",
"issue_date":"2016-05-05",
"created_at":1462453039,
"contact":{
"id":424230933,
"full_name":"Toxic Crusader"
},
"country":"US",
"street_line_1":null,
"street_line_2":null,
"city":null,
"region":null,
"postal_code":null,
"po_number":null,
"currency":"EUR",
"items":[{
"id":2621550,
"description":"Mop",
"quantity":"1.0",
"unit_price":"30.0",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":null,
"tax_2_name":"",
"tax_2_rate":null,
"reference":null,
"subtotal_cents":"3000",
"discount_cents":"0",
"gross_amount_cents":"3000"
}],
"subtotal_cents":"3000",
"discount_cents":"0",
"taxes":[],
"total_cents":"3000",
"payments":[{
"id":972444,
"date":"2016-05-05",
"payment_method":"credit_card",
"amount_cents":"3000"
}],
"notes":null,
"state":"paid",
"tag_list":[],
"secure_id":"f6a78e399aa6369e3b0329da78cc24534bc1934d",
"permalink":
"http://ACCOUNT_NAME.quadernoapp.com/receipt/f6a78e399aa6369e3b0329da78cc24534bc1934dzzRot",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/receipt/f6a78e399aa6369e3b0329da78cc24534bc1934dzzRot.pdf",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/receipts/13638223434.json",
"custom_metadata":{}
}
GET
ting from /receipts/RECEIPT_ID.json
will return that specific receipt.
If you have connected Quaderno and Stripe, you can also GET /stripe/charges/STRIPE_CHARGE_ID.json
to get the Quaderno receipt for a Stripe charge.
Update a receipt
PUT /receipts/RECEIPT_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"notes":"You better pay this time, Tony.", "custom_metadata":{"memo":"I think he is not paying again."}}' \
'https://ACCOUNT_NAME.quadernoapp.com/api/receipts/RECEIPT_ID.json'
params = {
notes: 'You better pay this time, Tony.',
custom_metadata: {
memo: 'I think he is not paying again.'
}
}
Quaderno::Receipt.update(RECEIPT_ID, params) #=> Quaderno::Receipt
$receipt->notes = 'You better pay this time, Tony.';
$receipt->custom_metadata = array('memo' => 'I think he is not paying again.');
$receipt->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"notes": "You better pay this time, Tony."
]
let updateReceipt = Receipt.update(RECEIPT_ID, params)
client.request(updateReceipt) { response in
// response will contain the result of the request.
}
PUT
ting to /receipts/RECEIPT_ID.json
will update the receipt with the passed parameters.
This will return 200 OK
along with the current JSON representation of the receipt if successful.
Delete a receipt
DELETE /receipts/RECEIPT_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/receipts/RECEIPT_ID.json'
Quaderno::Receipt.delete(RECEIPT_ID) #=> Boolean
$receipt->delete();
let client = Quaderno.Client(/* ... */)
let deleteReceipt = Receipt.delete(INVOICE_ID)
client.request(deleteReceipt) { response in
// response will contain the result of the request.
}
DELETE
ing to /receipt/RECEIPT_ID.json
will delete the specified receipt and returns 204 No Content
if successful.
Deliver (Send) a receipt
curl -u YOUR_API_KEY: \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/deliver.json'
receipt = Quaderno::Receipt.find(RECEIPT_ID)
receipt.deliver
$receipt->deliver(); // Return true (success) or false (error)
let client = Quaderno.Client(/* ... */)
let deliverReceipt = Receipt.deliver(INVOICE_ID)
client.request(deliverReceipt) { response in
// response will contain the result of the request.
}
GET
ting /receipts/RECEIPT_ID/deliver.json
will send the receipt to the assigned contact email. This will return 200 OK
if successful, along with a JSON representation of the receipt.
If the destination contact does not have an email address you will receive a 422 Unprocessable Entity
error.
Invoices
An invoice is a detailed list of goods shipped or services rendered, with an account of all costs.
Create an invoice
POST /invoices.json
# body.json
{
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"STARK",
"po_number":"",
"currency":"USD",
"tag_list":"playboy, businessman",
"items_attributes":[
{
"description":"Whiskey",
"quantity":"1.0",
"unit_price":"20.0",
"discount_rate":"0.0",
"reference":"item_code_X"
}
],
"custom_metadata":{
"a_custom_key":"a custom value"
}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/invoices.json'
$invoice = new QuadernoInvoice(array(
'po_number' => '',
'currency' => 'USD',
'tag_list' => array('playboy', 'businessman'),
'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
'description' => 'Pizza bagels',
'unit_price' => 9.99,
'quantity' => 20));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');
$invoice->addItem($item);
$invoice->addContact($contact);
$invoice->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('50603e722f412e0435000024') #=> Quaderno::Contact
params = {
contact_id: contact.id,
po_number: 'PO.12234',
currency: 'USD',
tag_list: ['playboy', 'businessman'],
items_attributes: [
{
description: 'Whiskey',
quantity: '1.0',
unit_price: '20.0',
discount_rate: '0.0'
}
],
custom_metadata: {
a_custom_key: 'a custom value'
}
}
invoice = Quaderno::Invoice.create(params) #=> Quaderno::Invoice
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"contact_id": "5059bdbf2f412e0901000024",
"contact_name": "STARK",
"po_number": "",
"currency": "USD",
"tag_list": ["playboy", "businessman"]
]
let createInvoice = Invoice.create(params)
client.request(createInvoice) { response in
// response will contain the result of the request.
}
POST
ing to /invoices.json
will create a new invoice from the parameters passed.
This will return 201 Created
and the current JSON representation of the invoice if the creation was a success, along with the location of the new invoice in the url
field.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
number | no | String(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness |
issue_date | no | String(255 chars) Available for all except for recurring . Defaults to the current date. Format YYYY-MM-DD |
po_number | no | String(255 chars) |
due_date | no | String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD |
currency | no | String(3 chars) Defaults to the account currency. En formato ISO 4217 |
tag_list | no | String(255 chars). Multiple tags should be separated by commas |
payment_details | no | Text |
notes | no | Text |
contact_id | (Mandatory if contact is not present) |
ID |
contact | (Mandatory if contact_id is not present) |
Hash with a contact data for creation |
country | no | String(2 chars) ISO 3166-1 alpha-2 . Available for updates |
street_line_1 | no | String(255 chars). Available for updates |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars). Available for updates |
region | no | String(255 chars). Available for updates |
postal_code | no | String(255 chars). Available for updates |
items_attributes | yes | Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items. |
payment_method | no | Create a paid document in a single request. One of the following: credit_card , cash , wire_transfer , direct_debit , check , promissory_note , iou , paypal or other |
custom_metadata | no | Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Document Item Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
id | no | ID. Available only for updates |
description | yes | String(255 chars) |
quantity | no | Decimal. Defaults to 1.0 |
unit_price | yes | Decimal |
total_amount | Mandatory if unit_price is not present |
Decimal |
discount_rate | no | Decimal |
tax_1_name | Mandatory if tax_1_rate is present |
String(255 chars) |
tax_1_rate | Mandatory if tax_1_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_1_country | no | String(2 chars). Defaults to the contact’s country |
tax_1_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
tax_2_name | Mandatory if tax_2_rate is present |
String(255 chars) |
tax_2_rate | Mandatory if tax_2_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_2_country | no | String(2 chars). Defaults to the contact’s country |
tax_2_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
reference | no | String(255 chars) Code (code ) of an existing item. If present none of the mandatory attributes are mandatory (as you are referencing an item that already exists) |
_destroy | no | Set it to 1 if you want to remove the document item selected by ID. Available only for updates |
Invoice States
Possible invoice states are:
outstanding
paid
late
archived
Create an attachment during invoice creation
{
"attachment":{
"data":"aBaSe64EnCoDeDFiLe",
"filename":"the_filename.png"
}
}
Optionally, you can pass an attachment hash along with the rest of the parameters.
Fields:
Field | Description |
---|---|
data |
Contains a Base64 encoded string which represents the file. |
filename |
The attachment file name. |
Valid file extensions are pdf
, txt
, jpeg
, jpg
, png
, xml
, xls
, doc
, rtf
and html
. Any other format will stop invoice creation and return a 422 Unprocessable Entity
error.
Retrieve: Get and filter all invoices
GET /invoices.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/invoices.json'
Quaderno::Invoice.all() #=> Array
$invoices = QuadernoInvoice::find(); // Returns an array of QuadernoInvoice
let client = Quaderno.Client(/* ... */)
let listInvoices = Invoice.list(pageNum)
client.request(listInvoices) { response in
// response will contain the result of the request.
}
[
{
"id":"507693322f412e0e2e00000f",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"contact":{
"id":"5073f9c22f412e02d0000032",
"full_name":"Garfield"
},
"country":"US",
"street_line_1":"Street 23",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"10203",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"lasagna",
"quantity":"25.0",
"unit_price_cents":"375",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Awesome!",
"subtotal_cents":"9375",
"discount_cents":"0",
"gross_amount_cents":"9375"
}
],
"subtotal_cents":"9375",
"discount_cents":"0",
"taxes":[],
"total_cents":"9375",
"payments":[
{
"id":"50aca7d92f412eda5200002c",
"date":"2012-11-21",
"payment_method":"credit_card",
"amount_cents":"9375",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/invoices/507693322f412e0e2e00000f/payments/50aca7d92f412eda5200002c.json"
},
],
"payment_details":"Ask Jon",
"notes":"",
"state":"paid",
"tag_list":["lasagna", "cat"],
"secure_id":"7hef1rs7p3rm4l1nk",
"permalink":"https://quadernoapp.com/invoice/7hef1rs7p3rm4l1nk",
"pdf":"https://quadernoapp.com/invoice/7hef1rs7p3rm4l1nk.pdf",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/invoices/507693322f412e0e2e00000f.json",
"custom_metadata":{}
},
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376020",
"contact":{
"id":"5073f9c22f412e02d00004cf",
"full_name":"Teenage Mutant Ninja Turtles"
},
"country":"US",
"street_line_1":"Melrose Ave, Sewer #3",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"481516234291",
"description":"pizza",
"quantity":"15.0",
"unit_price_cents":"6000",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Even the bad ones taste good!",
"subtotal_cents":"6000",
"discount_cents":"0",
"gross_amount_cents":"6000"
}
],
"subtotal_cents":"6000",
"discount_cents":"0",
"taxes":[],
"total_cents":"6000",
"payments":[],
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":["pizza", "turtles"],
"secure_id":"7hes3c0ndp3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/invoice/7hes3c0ndp3rm4l1nk",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/invoice/7hes3c0ndp3rm4l1nk.pdf",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/invoices/507693322f412e0e2e0000da.json",
"custom_metadata":{}
}
]
GET
ting from /invoices.json
will return all the user’s invoices.
You can filter the results in a few ways:
- By
number
,contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By contact, passing the contact ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single invoice
GET /invoices/INVOICE_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID.json'
Quaderno::Invoice.find(INVOICE_ID) #=> Quaderno::Invoice
$invoice = QuadernoInvoice::find('INVOICE_ID'); // Returns a QuadernoInvoice
let client = Quaderno.Client(/* ... */)
let readInvoice = Invoice.read(INVOICE_ID)
client.request(readInvoice) { response in
// response will contain the result of the request.
}
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"contact":{
"id":"5073f9c22f412e02d00004cf",
"full_name":"Teenage Mutant Ninja Turtles"
},
"country":"US",
"street_line_1":"Melrose Ave, Sewer #3",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"pizza",
"quantity":"15.0",
"unit_price_cents":"6000",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Even the bad ones taste good!",
"subtotal_cents":"6000",
"discount_cents":"0",
"gross_amount_cents":"6000"
}
],
"subtotal_cents":"6000",
"discount_cents":"0",
"taxes":[],
"total_cents":"6000",
"payments":[],
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":["lasagna", "cat"],
"secure_id":"7hef1rs7p3rm4l1nk",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/invoice/7hef1rs7p3rm4l1nk",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/invoice/7hef1rs7p3rm4l1nk.pdf",
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/invoices/507693322f412e0e2e0000da.json",
"custom_metadata":{}
}
GET
ting from /invoices/INVOICE_ID.json
will return that specific invoice.
If you have connected Quaderno and Stripe, you can also GET /stripe/charges/STRIPE_CHARGE_ID.json
to get the Quaderno invoice for a Stripe charge.
Update an invoice
PUT /invoices/INVOICE_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"notes":"You better pay this time, Tony."}' \
'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID.json'
params = {
notes: 'You better pay this time, Tony.'
}
Quaderno::Invoice.update(INVOICE_ID, params) #=> Quaderno::Invoice
$invoice->notes = 'You better pay this time, Tony.';
$invoice->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"notes": "You better pay this time, Tony."
]
let updateInvoice = Invoice.update(INVOICE_ID, params)
client.request(updateInvoice) { response in
// response will contain the result of the request.
}
PUT
ting to /invoices/INVOICE_ID.json
will update the invoice with the passed parameters. No more than 200 items are allowed per request. If you need to update more use subsequent requests.
This will return 200 OK
along with the current JSON representation of the invoice if successful.
Delete an invoice
DELETE /invoices/INVOICE_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID.json'
Quaderno::Invoice.delete(INVOICE_ID) #=> Boolean
$invoice->delete();
let client = Quaderno.Client(/* ... */)
let deleteInvoice = Invoice.delete(INVOICE_ID)
client.request(deleteInvoice) { response in
// response will contain the result of the request.
}
DELETE
ing to /invoices/INVOICE_ID.json
will delete the specified invoice and returns 204 No Content
if successful.
Deliver (Send) an invoice
curl -u YOUR_API_KEY: \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/deliver.json'
invoice = Quaderno::Invoice.find(INVOICE_ID)
invoice.deliver
$invoice->deliver(); // Return true (success) or false (error)
let client = Quaderno.Client(/* ... */)
let deliverInvoice = Invoice.deliver(INVOICE_ID)
client.request(deliverInvoice) { response in
// response will contain the result of the request.
}
GET
ting /invoices/INVOICE_ID/deliver.json
will send the invoice to the assigned contact email. This will return 200 OK
if successful, along with a JSON representation of the invoice.
If the destination contact does not have an email address you will receive a 422 Unprocessable Entity
error.
Credits
A credit note is a reverse invoice; something to cancel out - partially or completely - an invoice you have issued in the past. It may be for the full amount of an invoice or it may be for less in the case of a partial refund.
Create a credit
POST /credits.json
# body.json
{
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"STARK",
"po_number":"",
"currency":"USD",
"tag_list":"playboy, businessman",
"items_attributes":[
{
"description":"Whiskey",
"quantity":"1.0",
"unit_price":"20.0",
"discount_rate":"0.0",
"reference":"item_code_X"
}
],
"custom_metadata":{
"a_custom_key":"a custom value"
}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/credits.json'
$credit = new QuadernoCredit(array(
'po_number' => '',
'currency' => 'USD',
'tag_list' => array('playboy', 'businessman'),
'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
'description' => 'Whiskey',
'unit_price' => 20.0,
'reference' => 'ITEM_ID',
'quantity' => 1.0));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');
$credit->addItem($item);
$credit->addContact($contact);
$credit->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('50603e722f412e0435000024') #=> Quaderno::Contact
params = {
contact_id: contact.id,
contact_name: 'STARK',
po_number: '',
currency: 'USD',
tag_list: ['playboy', 'businessman'],
items_attributes: [
{
description: 'Whiskey',
quantity: '1.0',
unit_price: '20.0',
discount_rate: '0.0',
}
],
custom_metadata: {
a_custom_key: 'a custom value'
}
}
Quaderno::Credit.create(params) #=> Quaderno::Credit
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"contact_id": "5059bdbf2f412e0901000024",
"contact_name": "STARK",
"po_number": "",
"currency": "USD",
"tag_list": ["playboy", "businessman"]
]
let createCredit = Credit.create(params)
client.request(createCredit) { response in
// response will contain the result of the request.
}
POST
ing to /credits.json
will create a new credit from the parameters passed.
This will return 201 Created
and the current JSON representation of the credit if the creation was a success, along with the location of the new credit in the url
field.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
document_id | no (highly recommendable) | ID. ID of a related Invoice or Receipt. |
number | no | String(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness |
issue_date | no | String(255 chars) Available for all except for recurring . Defaults to the current date. Format YYYY-MM-DD |
po_number | no | String(255 chars) |
due_date | no | String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD |
currency | no | String(3 chars) Defaults to the account currency. En formato ISO 4217 |
tag_list | no | String(255 chars). Multiple tags should be separated by commas |
payment_details | no | Text |
notes | no | Text |
contact_id | (Mandatory if contact is not present) |
ID |
contact | (Mandatory if contact_id is not present) |
Hash with a contact data for creation |
country | no | String(2 chars) ISO 3166-1 alpha-2 . Available for updates |
street_line_1 | no | String(255 chars). Available for updates |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars). Available for updates |
region | no | String(255 chars). Available for updates |
postal_code | no | String(255 chars). Available for updates |
items_attributes | yes | Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items. |
payment_method | no | Create a paid document in a single request. One of the following: credit_card , cash , wire_transfer , direct_debit , check , promissory_note , iou , paypal or other |
custom_metadata | no | Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Document Item Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
id | no | ID. Available only for updates |
description | yes | String(255 chars) |
quantity | no | Decimal. Defaults to 1.0 |
unit_price | yes | Decimal |
total_amount | Mandatory if unit_price is not present |
Decimal. Cents version available as total_amount_cents |
discount_rate | no | Decimal |
tax_1_name | Mandatory if tax_1_rate is present |
String(255 chars) |
tax_1_rate | Mandatory if tax_1_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_1_country | no | String(2 chars). Defaults to the contact’s country |
tax_1_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
tax_2_name | Mandatory if tax_2_rate is present |
String(255 chars) |
tax_2_rate | Mandatory if tax_2_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_2_country | no | String(2 chars). Defaults to the contact’s country |
tax_2_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
reference | no | String(255 chars) Code (code ) of an existing item. If present none of the mandatory attributes are mandatory (as you are referencing an item that already exists) |
_destroy | no | Set it to 1 if you want to remove the document item selected by ID. Available only for updates |
Create a credit note from an existing invoice or receipt
Alternatively, you can create a credit note for the full amount of the original invoice or receipt by sending the parameter invoice_id
with the ID of the invoice or receipt you want to refund rather than all the credit note attributes in the body.
Credit States
Possible credit states are:
outstanding
paid
late
archived
Create an attachment during credit creation
{
"attachment":{
"data":"aBaSe64EnCoDeDFiLe",
"filename":"the_filename.png"
}
}
Optionally, you can pass an attachment hash along with the rest of the parameters.
Fields:
Field | Description |
---|---|
data |
Contains a Base64 encoded string which represents the file. |
filename |
The attachment file name. |
Valid file extensions are pdf
, txt
, jpeg
, jpg
, png
, xml
, xls
, doc
, rtf
and html
. Any other format will stop credit creation and return a 422 Unprocessable Entity
error.
Retrieve: Get and filter all credits
GET /credits.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/credits.json'
Quaderno::Credit.all() #=> Array
$credits = QuadernoCredit::find(); // Returns an array of QuadernoCredit
let client = Quaderno.Client(/* ... */)
let listCredits = Credit.list(pageNum)
client.request(listCredits) { response in
// response will contain the result of the request.
}
[
{
"id":"507693322f412e0e2e00000f",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"related_document":{
"id":977,
"type":"Invoice"
},
"contact":{
"id":"5073f9c22f412e02d0000032",
"full_name":"Garfield"
},
"country":"US",
"street_line_1":"Street 23",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"10203"
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"lasagna",
"quantity":"25.0",
"unit_price_cents":"375",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Awesome!",
"subtotal_cents":"9375",
"discount_cents":"0",
"gross_amount_cents":"9375"
}
],
"subtotal_cents":"9375",
"discount_cents":"0",
"taxes":[],
"total_cents":"9375",
"payments":[
{
"id":"50aca7d92f412eda5200002c",
"date":"2012-11-21",
"payment_method":"credit_card",
"amount_cents":"9375",
"url":"https://my-account.quadernoapp.com/api/credits/507693322f412e0e2e00000f/payments/50aca7d92f412eda5200002c.json"
},
],
"payment_details":"Ask Jon",
"notes":"",
"state":"paid",
"tag_list":["lasagna", "cat"],
"secure_id":"7hef1rs7p3rm4l1nk",
"permalink":"https://quadernoapp.com/credit/7hef1rs7p3rm4l1nk",
"pdf":"https://quadernoapp.com/credit/7hef1rs7p3rm4l1nk.pdf",
"url":"https://my-account.quadernoapp.com/api/credits/507693322f412e0e2e00000f",
"custom_metadata":{}
},
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376020",
"related_document":{
"id":978,
"type":"Invoice"
},
"contact":{
"id":"5073f9c22f412e02d00004cf",
"full_name":"Teenage Mutant Ninja Turtles"
},
"country":"US",
"street_line_1":"Melrose Ave, Sewer #3",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"481516234291",
"description":"pizza",
"quantity":"15.0",
"unit_price_cents":"6000",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Even the bad ones taste good!",
"subtotal_cents":"6000",
"discount_cents":"0",
"gross_amount_cents":"6000"
}
],
"subtotal_cents":"6000",
"discount_cents":"0",
"taxes":[],
"total_cents":"6000",
"payments":[],
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":["pizza", "turtles"],
"secure_id":"7hes3c0ndp3rm4l1nk",
"permalink":"https://my-account.quadernoapp.com/credit/7hes3c0ndp3rm4l1nk",
"pdf":"https://my-account.quadernoapp.com/credit/7hes3c0ndp3rm4l1nk.pdf",
"url":"https://my-account.quadernoapp.com/api/credits/507693322f412e0e2e0000da",
"custom_metadata":{}
}
]
GET
ting from /credits.json
will return all the user’s credits.
You can filter the results in a few ways:
- By
number
,contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By contact, passing the contact ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single credit
GET /credits/CREDIT_ID.json
curl -u YOUR_API_KEY:x \_cents
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID.json'
Quaderno::Credit.find(CREDIT_ID) #=> Quaderno::Credit
$credit = QuadernoCredit::find('CREDIT_ID'); // Returns a QuadernoCredit
let client = Quaderno.Client(/* ... */)
let readCredit = Credit.read(CREDIT_ID)
client.request(readCredit) { response in
// response will contain the result of the request.
}
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"contact":{
"id":"5073f9c22f412e02d00004cf",
"full_name":"Teenage Mutant Ninja Turtles"
},
"country":"US",
"street_line_1":"Melrose Ave, Sewer #3",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"pizza",
"quantity":"15.0",
"unit_price_cents":"6000",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Even the bad ones taste good!",
"subtotal_cents":"6000",
"discount_cents":"0",
"gross_amount_cents":"6000"
}
],
"subtotal_cents":"6000",
"discount_cents":"0",
"taxes":[],
"total_cents":"6000",
"payments":[],
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":["lasagna", "cat"],
"secure_id":"7hef1rs7p3rm4l1nk",
"permalink":"https://my-account.quadernoapp.com/credit/7hef1rs7p3rm4l1nk",
"pdf":"https://my-account.quadernoapp.com/credit/7hef1rs7p3rm4l1nk.pdf",
"url":"https://my-account.quadernoapp.com/api/credits/507693322f412e0e2e0000da",
"custom_metadata":{}
}
GET
ting from /credits/CREDIT_ID.json
will return that specific credit.
Update a credit
PUT /credits/CREDIT_ID.json
# body.json
{
"tag_list":"whiskey, alcohol",
"notes":"You better pay this time, Tony",
"region":"Genosha",
"custom_metadata":{"memo":"I think he is not paying again."}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID.json'
params = {
tag_list: ['whiskey', 'alcohol'],
notes: 'You better pay this time, Tony',
region: 'Genosha',
custom_metadata: {
memo: 'I think he is not paying again.'
}
}
Quaderno::Credit.update(CREDIT_ID, params) #=> Quaderno::Credit
$credit->tag_list = array("whiskey", "alcohol");
$credit->notes = "You better pay this time, Tony";
$credit->region = "Genosha";
$credit->custom_metadata = array("memo" => "I think he is not paying again.");
$credit->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"tag_list": ["whiskey", "alcohol"]
]
let updateCredit = Credit.update(CREDIT_ID, params)
client.request(updateCredit) { response in
// response will contain the result of the request.
}
PUT
ting to /credits/CREDIT_ID.json
will update the credit with the passed parameters.
This will return 200 OK
along with the current JSON representation of the credit if successful.
Delete a credit
DELETE /credits/CREDIT_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID.json'
Quaderno::Credit.delete(CREDIT_ID) #=> Boolean
$credit->delete();
let client = Quaderno.Client(/* ... */)
let deleteCredit = Credit.delete(CREDIT_ID)
client.request(deleteCredit) { response in
// response will contain the result of the request.
}
DELETE
ing to /credit/CREDIT_ID.json
will delete the specified credit and returns 204 No Content
if successful.
Deliver (Send) a credit
GET
ting /credits/CREDIT_ID/deliver.json
will send the invoice to the assigned contact email. This will return 200 OK
if successful, along with a JSON representation of the invoice.
If the destination contact does not have an email address you will receive a 422 Unprocessable Entity
error.
Expenses
Expenses are all the invoices that you receive from your vendors.
Create an expense
POST /expenses.json
# body.json
{
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"ACME",
"currency":"USD",
"items_attributes":[
{
"description":"Rocket launcher",
"quantity":"1.0",
"unit_price":"0.0",
"discount_rate":"0.0",
"reference":"item_code_X"
}
],
"custom_metadata":{
"a_custom_key":"a custom value"
}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \ 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses.json'
$expense = new QuadernoExpense(array(
'po_number' => '',
'currency' => 'USD',
'tag_list' => array('playboy', 'businessman'),
'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
'description' => 'Rocket launcher',
'unit_price' => 0.0,
'discount_rate' => 0.0,
'reference' => "ITEM_ID",
'quantity' => 1.0));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');
$expense->addItem($item);
$expense->addContact($contact);
$expense->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('5059bdbf2f412e0901000024') #=> Quaderno::Contact
params = {
contact_id: contact.id
currency: 'USD',
tag_list: ['playboy', 'businessman'],
items_attributes: [
{
description: 'Rocket launcher',
quantity: '1.0',
unit_price: '0.0',
discount_rate: '0.0'
}
],
custom_metadata: {
a_custom_key: 'a custom value'
}
}
invoice = Quaderno::Expense.create(params) #=> Quaderno::Expense
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"ACME",
"currency":"USD"
]
let createExpense = Expense.create(params)
client.request(createExpense) { response in
// response will contain the result of the request.
}
POST
ing to /expenses.json
will create a new expense from the parameters passed.
This will return 201 Created
and the current JSON representation of the expense if the creation was a success, along with the location of the new expense in the url
field.
Attribute | Mandatory | Type/Description |
---|---|---|
number | no | String(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness |
issue_date | no | String(255 chars) Available for all except for recurring . Defaults to the current date. Format YYYY-MM-DD |
po_number | no | String(255 chars) |
due_date | no | String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD |
currency | no | String(3 chars) Defaults to the account currency. En formato ISO 4217 |
tag_list | no | String(255 chars). Multiple tags should be separated by commas |
payment_details | no | Text |
notes | no | Text |
contact_id | (Mandatory if contact is not present) |
ID |
contact | (Mandatory if contact_id is not present) |
Hash with a contact data for creation |
country | no | String(2 chars) ISO 3166-1 alpha-2 . Available for updates |
street_line_1 | no | String(255 chars). Available for updates |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars). Available for updates |
region | no | String(255 chars). Available for updates |
postal_code | no | String(255 chars). Available for updates |
items_attributes | yes | Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items. |
custom_metadata | no | Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Document Item Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
id | no | ID. Available only for updates |
description | yes | String(255 chars) |
quantity | no | Decimal. Defaults to 1.0 |
unit_price | yes | Decimal |
total_amount | Mandatory if unit_price is not present |
Decimal |
discount_rate | no | Decimal |
tax_1_name | Mandatory if tax_1_rate is present |
String(255 chars) |
tax_1_rate | Mandatory if tax_1_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_1_country | no | String(2 chars). Defaults to the contact’s country |
tax_1_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
tax_2_name | Mandatory if tax_2_rate is present |
String(255 chars) |
tax_2_rate | Mandatory if tax_2_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_2_country | no | String(2 chars). Defaults to the contact’s country |
tax_2_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
reference | no | String(255 chars) Code (code ) of an existing item. If present none of the mandatory attributes are mandatory (as you are referencing an item that already exists) |
_destroy | no | Set it to 1 if you want to remove the document item selected by ID. Available only for updates |
Expense States
Possible expense states are:
outstanding
paid
late
archived
Create an attachment during expense creation
{
"attachment":{
"data":"aBaSe64EnCoDeDFiLe",
"filename":"the_filename.png"
}
}
Optionally, you can pass an attachment hash along with the rest of the parameters.
Fields:
Field | Description |
---|---|
data |
Contains a Base64 encoded string which represents the file. |
filename |
The attachment file name. |
Valid file extensions are pdf
, txt
, jpeg
, jpg
, png
, xml
, xls
, doc
, rtf
and html
. Any other format will stop expense creation and return a 422 Unprocessable Entity
error.
Retrieve: Get and filter all expenses
GET /expenses.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses.json'
Quaderno::Expense.all() #=> Array
$expenses = QuadernoExpense::find(); // Returns an array of QuadernoExpense
let client = Quaderno.Client(/* ... */)
let listExpenses = Expense.list(pageNum)
client.request(listExpenses) { response in
// response will contain the result of the request.
}
[
{
"id":"5076a6b92f412e0e2e00006c",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"contact":{
"id":"5059bdbf2f412e0901000024",
"full_name":"ACME"
},
"country":"US",
"street_line_1":"Verizon Center",
"street_line_2":"",
"city":"Washington DC",
"region":"Washington DC",
"postal_code":"",
"po_number":"",
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623423",
"description":"Rockets",
"quantity":"50.0",
"unit_price_cents":"12500",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"subtotal_cents":"625000",
"discount_cents":"0",
"gross_amount_cents":"625000"
}
],
"subtotal_cents":"625000",
"discount_cents":"0",
"taxes":[],
"total_cents":"625000",
"payments":[],
"tag_list":["rockets", "acme"],
"notes":"",
"state":"outstanding",
"url":"https://my-account.quadernoapp.com/api/expenses/5076a6b92f412e0e2e00006c",
"custom_metadata":{}
},
{
"id":"5076a6b92f412e0e2e00016d",
"issue_date":"2012-10-11",
"created_at":"1325376020",
"contact":{
"id":"5059bdbf2f412e0901000024",
"full_name":"ACME"
},
"country":"US",
"street_line_1":"Verizon Center",
"street_line_2":"",
"city":"Washington DC",
"region":"Washington DC",
"postal_code":"",
"po_number":"",
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":" 48151623424",
"description":"TNT",
"quantity":"100.0",
"unit_price_cents":"2500",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"subtotal_cents":"250000",
"discount_cents":"0",
"gross_amount_cents":"250000"
}
],
"subtotal_cents":"250000",
"discount_cents":"0",
"taxes":[],
"total_cents":"250000",
"payments":[],
"tag_list":["tnt", "acme"],
"notes":"",
"state":"outstanding",
"url":"https://my-account.quadernoapp.com/api/expenses/5076a6b92f412e0e2e00016d",
"custom_metadata":{}
}
]
GET
ting from /expenses.json
will return all the user’s expenses.
You can filter the results in a few ways:
- By
contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By specific vendor, passing the vendor ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single expense
GET /expenses/EXPENSE_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID.json'
expense = Quaderno::Expense.find(EXPENSE_ID) #=> Quaderno::Expense
$expense = QuadernoExpense::find('EXPENSE_ID'); // Returns a QuadernoExpense
let client = Quaderno.Client(/* ... */)
let readExpense = Expense.read(EXPENSE_ID)
client.request(readExpense) { response in
// response will contain the result of the request.
}
{
"id":"5076a6b92f412e0e2e00006c",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"contact":{
"id":"5059bdbf2f412e0901000024",
"full_name":"ACME"
},
"country":"US",
"street_line_1":"Verizon Center",
"street_line_2":"",
"city":"Washington DC",
"region":"Washington DC",
"postal_code":"",
"po_number":"",
"currency":"USD",
"items":[
{
"id":"48151623423",
"description":"Rockets",
"quantity":"50.0",
"unit_price_cents":"12500",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"subtotal_cents":"625000",
"discount_cents":"0",
"gross_amount_cents":"625000"
}
],
"subtotal_cents":"625000",
"discount_cents":"0",
"taxes":[],
"total_cents":"625000",
"payments":[],
"tag_list":["rockets", "acme"],
"notes":"",
"state":"outstanding",
"url":"https://my-account.quadernoapp.com/api/expenses/5076a6b92f412e0e2e00006c"
}
GET
ting from /expenses/EXPENSE_ID.json
will return that specific expense.
Update an expense
PUT /expenses/EXPENSE_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{ "payment_details":"Money in da bank" }' \
'https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID.json'
params = {
payment_details: 'Money in da bank'
}
Quaderno::Expense.update(EXPENSE_ID, params) #=> Quaderno::Expense
$expense->payment_details = 'Money in da bank';
$expense->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"payment_details": "Money in da bank"
]
let updateExpense = Expense.update(EXPENSE_ID, params)
client.request(updateExpense) { response in
// response will contain the result of the request.
}
PUT
ting to /expenses/EXPENSE_ID.json
will update the expense with the passed parameters.
This will return 200 OK
along with the current JSON representation of the expense if successful.
Save an invoice as an expense
{
"permalink":"c2d480801d0e577c6c3177ce0795c4bdaa480e22"
}
GET
ting /expenses/save.json
will save another account invoice as an expense using the permalink passed as a parameter.
This will return 201 Created
, with the current JSON representation of the expense if the creation was a success, along the location of the new expense in the url
field.
If the user does not have access to create new expenses, you’ll see 401 Unauthorized
or a 422
if the permalink is invalid or the invoice is not suitable to be saved.
Delete an expense
DELETE /expenses/EXPENSE_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID.json'
Quaderno::Expense.delete(EXPENSE_ID) #=> Boolean
$expense->delete();
let client = Quaderno.Client(/* ... */)
let deleteExpense = Expense.delete(EXPENSE_ID)
client.request(deleteExpense) { response in
// response will contain the result of the request.
}
DELETE
ing to /expense/EXPENSE_ID.json
will delete the specified contact and returns 204 No Content
if successful.
Estimates
An estimate is an offer that you give a client in order to get a specific job. With time, estimates are usually turned into issued invoices.
Create an estimate
POST /estimates.json
# body.json
{
"number":"0000006",
"contact_id":"50603e722f412e0435000024",
"contact_name":"Wild E. Coyote",
"po_number":"",
"currency":"EUR",
"items_attributes":[
{
"description":"ACME Catapult",
"quantity":"1.0",
"unit_price":"0.0",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"item_code_X"
}
],
"tag_list":"tnt",
"payment_details":"",
"notes":"",
"custom_metadata":{
"a_custom_key":"a custom value"
}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @- body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/estimates.json'
$estimate = new QuadernoEstimate(array(
'po_number' => '',
'currency' => 'USD',
'tag_list' => array('playboy', 'businessman'),
'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
'description' => 'ACME Catapult',
'unit_price' => 0.0,
'reference' => 'ITEM_ID',
'quantity' => 1.0));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');
$estimate->addItem($item);
$estimate->addContact($contact);
$estimate->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('50603e722f412e0435000024') #=> Quaderno::Contact
params = {
contact_id: contact.id,
number: '0000006',
po_number: '',
currency: 'EUR',
tag_list: 'tnt',
payment_details: '',
notes: '',
items_attributes: [
{
description: 'Whiskey',
quantity: '1.0',
unit_price: '20.0',
discount_rate: '0.0'
}
],
custom_metadata: {
a_custom_key: 'a custom value'
}
}
estimate = Quaderno::Estimate.create(params) #=> Quaderno::Estimate
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
number":"0000006",
"contact_id":"50603e722f412e0435000024",
"contact_name":"Wild E. Coyote",
"po_number":"",
"currency":"EUR",
"tag_list": ["playboy", "businessman"]
]
let createEstimate = Estimate.create(params)
client.request(createEstimate) { response in
// response will contain the result of the request.
}
POST
ing to /estimates.json
will create a new estimate from the parameters passed.
This will return 201 Created
and the current JSON representation of the estimate if the creation was a success, along with the location of the new estimate in the url
field.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
number | no | String(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness |
issue_date | no | String(255 chars) Available for all except for recurring . Defaults to the current date. Format YYYY-MM-DD |
po_number | no | String(255 chars) |
due_date | no | String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD |
currency | no | String(3 chars) Defaults to the account currency. En formato ISO 4217 |
tag_list | no | String(255 chars). Multiple tags should be separated by commas |
payment_details | no | Text |
notes | no | Text |
contact_id | (Mandatory if contact is not present) |
ID |
contact | (Mandatory if contact_id is not present) |
Hash with a contact data for creation |
country | no | String(2 chars) ISO 3166-1 alpha-2 . Available for updates |
street_line_1 | no | String(255 chars). Available for updates |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars). Available for updates |
region | no | String(255 chars). Available for updates |
postal_code | no | String(255 chars). Available for updates |
items_attributes | yes | Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests |
custom_metadata | no | Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Document Item Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
id | no | ID. Available only for updates |
description | yes | String(255 chars) |
quantity | no | Decimal. Defaults to 1.0 |
unit_price | yes | Decimal |
total_amount | Mandatory if unit_price is not present |
Decimal. Cents version available as total_amount_cents |
discount_rate | no | Decimal |
tax_1_name | Mandatory if tax_1_rate is present |
String(255 chars) |
tax_1_rate | Mandatory if tax_1_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_1_country | no | String(2 chars). Defaults to the contact’s country |
tax_1_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
tax_2_name | Mandatory if tax_2_rate is present |
String(255 chars) |
tax_2_rate | Mandatory if tax_2_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_2_country | no | String(2 chars). Defaults to the contact’s country |
tax_2_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
reference | no | String(255 chars) Code (code ) of an existing item. If present none of the mandatory attributes are mandatory (as you are referencing an item that already exists) |
_destroy | no | Set it to 1 if you want to remove the document item selected by ID. Available only for updates |
Estimate States
Possible estimate states are:
outstanding
accepted
declined
invoiced
late
Create an attachment during estimate creation
{
"attachment":{
"data":"aBaSe64EnCoDeDFiLe",
"filename":"the_filename.png"
}
}
Optionally, you can pass an attachment hash along with the rest of the parameters.
Fields:
Field | Description |
---|---|
data |
Contains a Base64 encoded string which represents the file. |
filename |
The attachment file name. |
Valid file extensions are pdf
, txt
, jpeg
, jpg
, png
, xml
, xls
, doc
, rtf
and html
. Any other format will stop estimate creation and return a 422 Unprocessable Entity
error.
Retrieve: Get and filter all estimates
GET /estimates.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/estimates.json'
Quaderno::Estimate.all() #=> Array
$estimates = QuadernoEstimate::find(); // Returns an array of QuadernoEstimate
let client = Quaderno.Client(/* ... */)
let listEstimates = Estimate.list(pageNum)
client.request(listEstimates) { response in
// response will contain the result of the request.
}
[
{
"id":"50603e722f412e0435000024",
"number":"0000003",
"issue_date":"2012-09-24",
"created_at":"1325376000",
"contact":{
"id":"5059bdbf2f412e0901000024",
"full_name":"Wild E. Coyote"
},
"country":"US",
"street_line_1":"Desert of New Mexico",
"street_line_2":"",
"city":"New Mexico",
"region":"New Mexico",
"postal_code":"",
"po_number":"",
"currency":"EUR",
"items":[
{
"id":"4815162342",
"description":"ACME TNT",
"quantity":"1.0",
"unit_price_cents":"10000",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"subtotal_cents":"10000",
"discount_cents":"0",
"gross_amount_cents":"10000"
}
],
"subtotal_cents":"10000",
"discount_cents":"0",
"taxes":[],
"total_cents":"10000",
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":[],
"permalink":"https://my-account.quadernoapp.com/estimate/7hef1rs7p3rm4l1nk",
"url":"https://my-account.quadernoapp.com/api/estimates/50603e722f412e0435000024.json",
"custom_metadata":{}
},
{
"id":"50603e722f412e0435000144",
"number":"0000005",
"issue_date":"2012-09-24",
"created_at":"1325376020",
"contact":{
"id":"5059bdbf2f412e0901000044",
"full_name":"Cookie Monster"
},
"country":"US",
"street_line_1":"Sesame Street",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"",
"po_number":"",
"currency":"EUR",
"items":[
{
"id":"48151623421",
"description":"Cookies",
"quantity":"5.0",
"unit_price_cents":"195",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"subtotal_cents":"975",
"discount_cents":"0",
"gross_amount_cents":"975"
}
],
"subtotal_cents":"975",
"discount_cents":"0",
"taxes":[],
"total_cents":"975",
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":[],
"permalink":"https://my-account.quadernoapp.com/estimate/7hes3c0ndp3rm4l1nk",
"url":"https://my-account.quadernoapp.com/api/estimates/50603e722f412e0435000144.json",
"custom_metadata":{}
}
]
GET
ting from /estimates.json
will return all the user’s estimates.
You can filter the results in a few ways:
- By
number
,contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By contact, passing the contact ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single estimate
GET /estimates/ESTIMATE_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/estimates/ESTIMATE_ID.json'
Quaderno::Estimate.find(ESTIMATE_ID) #=> Quaderno::Estimate
$estimate = QuadernoEstimate::find('ESTIMATE_ID'); // Returns a QuadernoEstimate
let client = Quaderno.Client(/* ... */)
let readEstimate = Estimate.read(ESTIMATE_ID)
client.request(readEstimate) { response in
// response will contain the result of the request.
}
{
"id":"50603e722f412e0435000024",
"number":"0000003",
"issue_date":"2012-09-24",
"created_at":"1325376000",
"contact":{
"id":"5059bdbf2f412e0901000024",
"full_name":"Wild E. Coyote"
},
"country":"US",
"street_line_1":"Desert of New Mexico",
"street_line_2":"",
"city":"New Mexico",
"region":"New Mexico",
"postal_code":"",
"po_number":"",
"currency":"EUR",
"items":[
{
"id":"4815162342"
"description":"ACME TNT",
"quantity":"1.0",
"unit_price_cents":"10000",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"subtotal_cents":"10000",
"discount_cents":"0",
"gross_amount_cents":"10000"
}
],
"subtotal_cents":"10000",
"discount_cents":"0",
"taxes":[],
"total":"10000",
"payment_details":"",
"notes":"",
"state":"outstanding",
"tag_list":[],
"permalink":"https://my-account.quadernoapp.com/estimate/7hef1rs7p3rm4l1nk",
"url":"https://my-account.quadernoapp.com/api/estimates/50603e722f412e0435000024.json",
"custom_metadata":{}
}
GET
ting from /estimates/ESTIMATE_ID.json
will return that specific estimate.
Update an estimate
PUT /estimates/ESTIMATE_ID.json
# body.json
{
"tag_list":"Wacky, racer"
"contact_id":"505c3b402f412e0248000044",
"contact_name":"Dick Dastardly",
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/estimates/ESTIMATE_ID.json'
params = {
contact_id: '505c3b402f412e0248000044',
contact_name: 'Dick Dastardly'
}
Quaderno::Estimate.update(ESTIMATE_ID, params) #=> Quaderno::Estimate
$estimate->contact_id = '505c3b402f412e0248000044';
$estimate->contact_name = 'Dick Dastardly';
$estimate->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"contact_name": "Dick Dastardly",
"contact_id": "505c3b402f412e0248000044"
]
let updateEstimate = Estimate.update(ESTIMATE_ID, params)
client.request(updateEstimate) { response in
// response will contain the result of the request.
}
PUT
ting to /estimates/ESTIMATE_ID.json
will update the estimate with the passed parameters.
This will return 200 OK
along with the current JSON representation of the estimate if successful.
Delete an estimate
DELETE /estimates/ESTIMATE_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/estimates/ESTIMATE_ID.json'
Quaderno::Estimate.delete(ESTIMATE_ID) #=> Boolean
$estimate->delete();
let client = Quaderno.Client(/* ... */)
let deleteEstimate = Estimate.delete(ESTIMATE_ID)
client.request(deleteEstimate) { response in
// response will contain the result of the request.
}
DELETE
ing to /estimate/ESTIMATE_ID.json
will delete the specified estimate and returns 204 No Content
if successful.
Deliver (Send) an estimate
curl -u YOUR_API_KEY:
-X GET
'https://ACCOUNT_NAME.quadernoapp.com/api/estimates/ESTIMATE_ID/deliver.json'
estimate = Quaderno::Estimate.find(ESTIMATE_ID)
estimate.deliver
$estimate->deliver(); // Return true (success) or false (error)
let client = Quaderno.Client(/* ... */)
let deliverEstimate = Estimate.deliver(ESTIMATE_ID)
client.request(deliverEstimate) { response in
// response will contain the result of the request.
}
GET
ting /estimates/ESTIMATE_ID/deliver.json
will send the estimate to the assigned contact email. This will return 200 OK
if successful, along with a JSON representation of the estimate.
If the destination contact does not have an email address you will receive a 422 Unprocessable Entity
error.
Recurring
A recurring is a special document that periodically renews itself and generates a recurring invoice or expense.
Create a recurring
POST /recurring.json
# body.json
{ "recurring_document":"expense",
"frequency":"monthly",
"start_date":"2015-08-01",
"contact_id":"5059bdbf2f412e0901000024",
"delivery":"send",
"contact_name":"STARK",
"po_number":"",
"currency":"USD",
"tag_list":"playboy, businessman",
"items_attributes":[
{
"description":"Whiskey",
"quantity":"1.0",
"unit_price":"20.0",
"discount_rate":"0.0",
"reference":"item_code_X"
}
],
"custom_metadata":{
"a_custom_key":"a custom value"
}
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/recurring.json'
$recurring = new QuadernoRecurring(array(
'po_number' => '',
'frequency' => 'monthly',
'start_date' => new DateTime('2015-08-01'),
'currency' => 'USD',
'tag_list' => array('playboy', 'businessman'),
'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
'description' => 'Pizza bagels',
'unit_price' => 9.99,
'quantity' => 20));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');
$recurring->custom_metadata = array('a_custom_key' => 'a custom value');
$recurring->addItem($item);
$recurring->addContact($contact);
$recurring->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('50603e722f412e0435000024') #=> Quaderno::Contact
params = {
contact_id: contact.id,
po_number: '',
currency: 'USD',
tag_list: ['playboy', 'businessman'],
frequency: 'monthly',
start_date: Date.parse('2015-08-01'),
items_attributes: [
{
description: 'Whiskey',
quantity: '1.0',
unit_price: '20.0',
discount_rate: '0.0'
}
],
custom_metadata: {
a_custom_key: 'a custom value'
}
}
recurring = Quaderno::Recurring.create(params) #=> Quaderno::Recurring
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"frequency": "monthly",
"start_date": "2015-08-01",
"contact_id": "5059bdbf2f412e0901000024",
"contact_name": "STARK",
"po_number": "",
"currency": "USD",
"tag_list": ["playboy", "businessman"]
]
let createRecurring = Recurring.create(params)
client.request(createRecurring) { response in
// response will contain the result of the request.
}
POST
ing to /recurring.json
will create a new recurring from the parameters passed.
This will return 201 Created
and the current JSON representation of the recurring if the creation was a success, along with the location of the new recurring in the url
field.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
number | no | String(255 chars) Available for recurrings, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness |
issue_date | no | String(255 chars) Available for all except for recurring . Defaults to the current date. Format YYYY-MM-DD |
po_number | no | String(255 chars) |
due_date | no | String(255 chars) Available for recurrings, expenses and credit notes. Format YYYY-MM-DD |
currency | no | String(3 chars) Defaults to the account currency. En formato ISO 4217 |
tag_list | no | String(255 chars). Multiple tags should be separated by commas |
payment_details | no | Text |
notes | no | Text |
contact_id | (Mandatory if contact is not present) |
ID |
contact | (Mandatory if contact_id is not present) |
Hash with a contact data for creation |
street_line_1 | no | String(255 chars). Available for updates |
street_line_2 | no | String(255 chars) |
city | no | String(255 chars). Available for updates |
region | no | String(255 chars). Available for updates |
postal_code | no | String(255 chars). Available for updates |
items_attributes | yes | Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items. |
payment_method | no | Create a paid document in a single request. One of the following: credit_card , cash , wire_transfer , direct_debit , check , promissory_note , iou , paypal or other |
recurring_document | no | invoice or expense . Defaults to invoice |
start_date | yes | Format YYYY-MM-DD . |
end_date | no | Format YYYY-MM-DD . |
frequency | no | One of these values: daily , weekly , biweekly , monthly , bimonthly , quarterly , semiyearly , yearly , biyearly . Defaults to monthly . |
delivery | no | One of these values: send , create , nothing . Defaults to send . |
due_days | no | Positvive integer |
custom_metadata | no | Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Document Item Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
id | no | ID. Available only for updates |
description | yes | String(255 chars) |
quantity | no | Decimal. Defaults to 1.0 |
unit_price | yes | Decimal |
total_amount | Mandatory if unit_price is not present |
Decimal |
discount_rate | no | Decimal |
tax_1_name | Mandatory if tax_1_rate is present |
String(255 chars) |
tax_1_rate | Mandatory if tax_1_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_1_country | no | String(2 chars). Defaults to the contact’s country |
tax_1_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_1_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
tax_2_name | Mandatory if tax_2_rate is present |
String(255 chars) |
tax_2_rate | Mandatory if tax_2_name is present |
Decimal between -100.00 and 100.00 (not included) |
tax_2_country | no | String(2 chars). Defaults to the contact’s country |
tax_2_region | no | String(255 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_county | no | String(2 chars). Recommendable to set for Canadian or United States taxes. |
tax_2_transaction_type | no | String. Accepts eservice , ebook or standard . Defaults to your account “default tax type”. |
reference | no | String(255 chars) Code (code ) of an existing item. If present none of the mandatory attributes are mandatory (as you are referencing an item that already exists) |
_destroy | no | Set it to 1 if you want to remove the document item selected by ID. Available only for updates |
Recurring States
Possible recurring states are:
active
archived
Create an attachment during recurring creation
{
"attachment":{
"data":"aBaSe64EnCoDeDFiLe",
"filename":"the_filename.png"
}
}
Optionally, you can pass an attachment hash along with the rest of the parameters.
Fields:
Field | Description |
---|---|
data |
Contains a Base64 encoded string which represents the file. |
filename |
The attachment file name. |
Valid file extensions are pdf
, txt
, jpeg
, jpg
, png
, xml
, xls
, doc
, rtf
and html
. Any other format will stop recurring creation and return a 422 Unprocessable Entity
error.
Retrieve: Get and filter all recurrings
GET /recurring.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/recurring.json'
Quaderno::Recurring.all() #=> Array
$recurrings = QuadernoRecurring::find(); // Returns an array of QuadernoRecurring
let client = Quaderno.Client(/* ... */)
let listRecurring = Recurring.list(pageNum)
client.request(listRecurring) { response in
// response will contain the result of the request.
}
[
{
"id":642069232,
"recurring_document":"expense",
"start_date":"2015-02-21",
"end_date":"2015-06-21",
"frequency":"monthly",
"recurring_period":"months",
"recurring_frequency":"1",
"contact":{
"id":176938,
"full_name":"Chuck Testa"
},
"po_number":"",
"due_days":null,
"currency":"EUR",
"items":[
{
"id":1589108,
"description":"Something corporate",
"quantity":"1.0",
"unit_price_cents":"2300",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":null,
"tax_2_name":"",
"tax_2_rate":null,
"reference":"",
"subtotal_cents":"2300",
"discount_cents":"0",
"gross_amount_cents":"2300"
}
],
"subtotal_cents":"2300",
"discount_cents":"0",
"taxes":[],
"total_cents":"2300",
"payment_details":"",
"notes":"",
"state":"archived",
"tag_list":[],
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/recurring/642069232.json",
"custom_metadata":{}
},
{
"id":6420739232,
"recurring_document":"recurring",
"start_date":"2015-01-02",
"end_date":null,
"frequency":"weekly",
"recurring_period":"weeks",
"recurring_frequency":"1",
"contact":{
"id":176939,
"full_name":"Veronica Black"
},
"po_number":"",
"due_days":null,
"currency":"EUR",
"items":[
{
"id":1589114,
"description":"Friday stuff",
"quantity":"1.0",
"unit_price_cents":"3400",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":null,
"tax_2_name":"",
"tax_2_rate":null,
"reference":"",
"subtotal_cents":"3400",
"discount_cents":"0",
"gross_amount_cents":"3400"
}
],
"subtotal_cents":"3400",
"discount_cents":"0",
"taxes":[],
"total_cents":"3400",
"payment_details":"",
"notes":"",
"state":"active",
"tag_list":[],
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/recurring/6420739232.json",
"custom_metadata":{}
}
]
GET
ting from /recurring.json
will return all the user’s recurrings.
You can filter the results in a few ways:
- By
number
,contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By contact, passing the contact ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single recurring
GET /recurring/RECURRING_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/recurring/RECURRING_ID.json'
Quaderno::Recurring.find(RECURRING_ID) #=> Quaderno::Recurring
$recurring = QuadernoRecurring::find('RECURRING_ID'); // Returns a QuadernoRecurring
let client = Quaderno.Client(/* ... */)
let readRecurring = Recurring.read(RECURRING_ID)
client.request(readRecurring) { response in
// response will contain the result of the request.
}
{
"id":6420739232,
"recurring_document":"expense",
"start_date":"2015-01-02",
"end_date":null,
"frequency":"weekly",
"recurring_period":"weeks",
"recurring_frequency":"1",
"contact":{
"id":176939,
"full_name":"Veronica Black"
},
"po_number":"",
"due_days":null,
"currency":"EUR",
"items":[
{
"id":1589114,
"description":"Friday stuff",
"quantity":"1.0",
"unit_price_cents":"3400",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":null,
"tax_2_name":"",
"tax_2_rate":null,
"reference":"",
"subtotal_cents":"3400",
"discount_cents":"0",
"gross_amount_cents":"3400"
}
],
"subtotal_cents":"3400",
"discount_cents":"0",
"taxes":[],
"total_cents":"3400",
"payment_details":"",
"notes":"",
"state":"active",
"tag_list":[],
"url":"https://ACCOUNT_NAME.quadernoapp.com/api/recurring/6420739232.json",
"custom_metadata":{}
}
GET
ting from /recurring/RECURRING_ID.json
will return that specific recurring.
If you have connected Quaderno and Stripe, you can also GET /stripe/charges/STRIPE_CHARGE_ID.json
to get the Quaderno recurring for a Stripe charge.
Update a recurring
PUT /recurring/RECURRING_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{"notes":"You better pay this time, Tony."}' \
'https://ACCOUNT_NAME.quadernoapp.com/api/recurring/RECURRING_ID.json'
params = {
notes: 'You better pay this time, Tony.'
}
Quaderno::Recurring.update(RECURRING_ID, params) #=> Quaderno::Recurring
$recurring->notes = 'You better pay this time, Tony.';
$recurring->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"notes": "You better pay this time, Tony."
]
let updateRecurring = Recurring.update(RECURRING_ID, params)
client.request(updateRecurring) { response in
// response will contain the result of the request.
}
PUT
ting to /recurring/RECURRING_ID.json
will update the recurring with the passed parameters.
This will return 200 OK
along with the current JSON representation of the recurring if successful.
Delete a recurring
DELETE /recurring/RECURRING_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/recurring/RECURRING_ID.json'
Quaderno::Recurring.delete(RECURRING_ID) #=> Boolean
$recurring->delete();
let client = Quaderno.Client(/* ... */)
let deleteRecurring = Recurring.delete(RECURRING_ID)
client.request(deleteRecurring) { response in
// response will contain the result of the request.
}
DELETE
ing to /recurring/RECURRING_ID.json
will delete the specified recurring and returns 204 No Content
if successful.
Items
The items are those products or services that you sell to your customers.
Items are their own object in Quaderno, but they can be referenced, in an array, by a few types of record:
- Invoices and sales receipts
- Expenses
- Credit notes
Create an item
POST /items.json
# body.json
{
"code":"BluRay 0003",
"name":"Titanic IV: The revenge",
"unit_cost":"15.0",
"tax_1_name":"AWESOME_TAX",
"tax_1_rate":"7.00",
"tax_2_name":"ANOTHER_AWESOME_TAX",
"tax_2_rate":"10.00",
"stock":"1000"
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/items.json'
$item = new QuadernoItem(array(
'name' => 'Jelly pizza',
'code' => 'Yummy',
'unit_cost' => '15.00',
'tax_1_name' => 'JUNKTAX',
'tax_1_rate' => '99.99'));
$item->save(); // Returns true (success) or false (error)
params = {
name: 'Jelly pizza',
code: 'Yummy',
unit_cost: '15.00',
tax_1_name: 'JUNKTAX',
tax_1_rate: '99.99'
}
Quaderno::Item.create(params) #=> Quaderno::Item
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"name":"Jelly pizza",
"code":"Yummy",
"unit_cost":"15.00",
"tax_1_name":"JUNKTAX",
"tax_1_rate":"99.99"
]
let createItem = Item.create(params)
client.request(createItem) { response in
// response will contain the result of the request.
}
POST
ing to /items.json
will create a new item from the parameters passed.
This will return 201 Created
and the current JSON representation of the item if the creation was a success, along with the location of the new item in the url
field.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
code | Mandatory if stock is present |
String(255 chars). Validates uniqueness |
name | yes | String(255 chars). |
unit_cost | yes | Decimal |
tax_1_name | Mandatory if tax_1_rate and tax_1_country are present) |
|
tax_1_rate | Mandatory if tax_1_name and tax_1_country are present) |
Decimal between -100.00 and 100.00 (not incluided) |
tax_1_country | Mandatory if tax_1_name and tax_1_rate are present) |
String(2 chars). Format ISO 3166-1 alpha-2 |
tax_2_name | Mandatory if tax_2_rate and tax_2_country are present) |
|
tax_2_rate | Mandatory if tax_2_name and tax_2_country are present) |
Decimal between -100.00 and 100.00 (not incluided) |
tax_2_country | Mandatory if tax_2_name and tax_2_rate are present) |
String(2 chars). Format ISO 3166-1 alpha-2 |
stock | no | Decimal |
Retrieve: Get and filter all items
GET /items.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/items.json'
Quaderno::Item.all() #=> Array
$items = QuadernoItem::find(); // Returns an array of QuadernoItem
let client = Quaderno.Client(/* ... */)
let readItem = Item.list(pageNum)
client.request(readItem) { response in
// response will contain the result of the request.
}
[
{
"id":1,
"code":"BluRay 0000",
"name":"Titanic",
"unit_cost":"15.0",
"stock":"100",
"url":"https://my-account.quadernoapp.com/api/items/1"
},
{
"id":2,
"code":"BluRay 0001",
"name":"Titanic II: The revenge",
"unit_cost":"15.0",
"tax_1_name":"AWESOME_TAX",
"tax_1_rate":"7.00",
"url":"https://my-account.quadernoapp.com/api/items/2"
},
{
"id":3,
"code":"BluRay 0002",
"name":"Titanic III: The origin",
"unit_cost":"15.0",
"stock":"33",
"url":"https://my-account.quadernoapp.com/api/items/3"
}
]
GET
ting from /items.json
will return all the user’s items.
You can filter the results in a few ways:
- By
number
,contact_name
orpo_number
by passing theq
parameter in the url like?q=KEYWORD
. - By date range, passing the
date
parameter in the url like?date=DATE1,DATE2
. - By state, passing the
state
parameter like?state=STATE
. - By contact, passing the contact ID in the
contact
parameter, like?contact=3231
.
Retrieve: Get a single item
GET /items/ITEM_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/items/ITEM_ID.json'
Quaderno::Item.find(ITEM_ID) #=> Quaderno::Item
$item = QuadernoItem::find('ITEM_ID'); // Returns a QuadernoItem
let client = Quaderno.Client(/* ... */)
let readItem = Item.list(pageNum)
client.request(readItem) { response in
// response will contain the result of the request.
}
{
"id":2,
"code":"BluRay 0001",
"name":"Titanic II: The revenge",
"unit_cost":"15.0",
"tax_1_name":"AWESOME_TAX",
"tax_1_rate":"7.00",
"url":"https://my-account.quadernoapp.com/api/items/2"
}
GET
ting from /items/ITEM_ID.json
will return that specific item.
Update an item
PUT /items/ITEM_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{ "unit_cost":"10.0" }' \
'https://ACCOUNT_NAME.quadernoapp.com/api/items/ITEM_ID.json'
params = {
unit_cost: '10.0',
}
Quaderno::Item.update(ITEM_ID, params) #=> Quaderno::Item
$item->unit_cost = '10.0';
$item->save();
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"unit_cost": "10.0"
]
let updateItem = Item.update(ITEM_ID, params)
client.request(updateItem) { response in
// response will contain the result of the request.
}
PUT
ting to /items/ITEM_ID.json
will update the item with the passed parameters.
This will return 200 OK
along with the current JSON representation of the item if successful.
Delete an item
DELETE /items/ITEM_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/items/ITEM_ID.json'
Quaderno::Item.delete(ITEM_ID) #=> Boolean
$item->delete();
let client = Quaderno.Client(/* ... */)
let deleteItem = Item.delete(ITEM_ID)
client.request(deleteItem) { response in
// response will contain the result of the request.
}
DELETE
ing to /item/ITEM_ID.json
will delete the specified item and returns 204 No Content
if successful.
Adding an item to a document by reference
Given this item:
{
"code":"BluRay 0003",
"name":"Titanic IV: The revenge",
"unit_cost":"15.0",
"tax_1_name":"AWESOME_TAX",
"tax_1_rate":"7.00",
"tax_2_name":"ANOTHER_AWESOME_TAX",
"tax_2_rate":"10.00",
"stock":"1000"
}
You can generate an invoice with this item by passing this data via POST to /invoices.json:
{
"contact_id":"5059bdbf2f412e0901000024",
"contact_name":"STARK",
"currency":"USD",
"tag_list":"playboy, businessman",
"items_attributes":[
{
"reference":"BluRay 0003",
"quantity":"2",
"unit_cost":"10.95"
}
],
}
You can use an item attributes to create or update a document (invoice, expense or estimate). In order to use this feature, the item must have a code
that must be set as a reference
in the items_attributes
array.
Then you can use the code
in the reference
field along with per-use attributes such as quantity
when setting the item_attributes
of another document, such as an invoice or expense.
Payments
Payments in Quaderno-lingo represent the recording of a successful payment.
Like items, payments are a sub-object and can be attached to multiple types of other records:
- Invoices and sales receipts
- Expenses
Create a payment
POST /invoices/INVOICE_ID/payments.json
orPOST /expenses/EXPENSE_ID/payments.json
# body.json
{
"amount":"56.60",
"payment_method":"credit_card"
}
# curl command
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/payments.json'
$payment = new QuadernoPayment(array(
'date' => date('2012-10-10'),
'payment_method' => 'credit_card'),
'amount' => '56.60');
$invoice->addPayment($payment); // Return true (success) or false (error)
$invoice->save(); // Returns true (success) or false (error)
invoice = Quaderno::Invoice.find(invoice_id)
params = {
payment_method: 'credit_card',
amount: '56.60'
}
invoice.add_payment(params) #=> Quaderno::Payment
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"payment_method": "credit_card",
"amount": "56.60"
]
let readInvoice = Invoice.read(INVOICE_ID)
client.request(readInvoice) { response in
// response will contain the result of the request.
}
POST
ing to invoices/INVOICE_ID/payments.json
or expenses/EXPENSE_ID/payments.json
will create a new payment from the parameters passed. Note that payments can only be created as an attachment to an invoice or expense.
This will return 201 Created
and the current JSON representation of the payment if the creation was a success, along with the location of the new item in the Location
header.
Attributes
Attribute | Mandatory | Type/Description |
---|---|---|
amount | yes | Decimal |
date | no | String(255 chars) Format YYYY-MM-DD |
payment_method | yes | One of the following: credit_card , cash , wire_transfer , direct_debit , check , promissory_note , iou , paypal or other |
Retrieve: Get all payments on an invoice or expense
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID/payments.json'
$expense = QuadernoExpense::find('EXPENSE_ID'); // Returns a QuadernoExpense
$payments = $expense->getPayments(); // Returns an array of QuadernoPayment
expense = Quaderno::Expense.find(EXPENSE_ID) #=> Quaderno::Expense
payments = expense.payments #=> an array of Quaderno::Payment
GET
ting from /invoices/INVOICE_ID/payments.json
or /expenses/EXPENSE_ID/payments.json
will return all the payments on the invoice or expense in question.
Retrieve: Get a single payment on an invoice or expense
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID/payments/PAYMENT_ID.json'
$expense = QuadernoExpense::find('EXPENSE_ID'); // Returns a QuadernoExpense
$payments = $expense->getPayments(); // Returns an array of QuadernoPayment to iterate through
expense = Quaderno::Expense.find(EXPENSE_ID) #=> Quaderno::Expense
payments = expense.payments #=> an array of Quaderno::Payment to iterate through
GET
ting from /invoices/INVOICE_ID/payments/PAYMENT_ID.json
or /expenses/EXPENSE_ID/payments/PAYMENT_ID.json
will return that payments on the invoice or expense in question, if it exists.
Delete a payment
DELETE /invoices/INVOICE_ID/payments/PAYMENT_ID.json
orDELETE /expenses/EXPENSE_ID/payments/PAYMENT_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID/payments/PAYMENT_ID.json'
invoice = Quaderno::Invoice.find(invoice_id)
invoice.remove_payment(payment_id) #=> Boolean
$expense->removePayment($payments[2]); // Return true (success) or false (error)
DELETE
ing to /invoices/INVOICE_ID/payments/PAYMENT_ID.json
or /expenses/EXPENSE_ID/payments/PAYMENT_ID.json
will delete the specified payment and returns 204 No Content
if successful.
Taxes
One of the killer features Quaderno provides is efficient and easy tax management. As part of this, we offer an API for easy tax calculation.
Calculate Taxes
GET /taxes/calculate.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/taxes/calculate.json?country=ES&postal_code=08080&vat_number=ESA58818501'
params = {
country: 'ES',
postal_code: '08080'
vat_number: 'ESA58818501'
}
tax = Quaderno::Tax.calculate(params) #=> Quaderno::Tax
$data = array(
'country' => 'ES',
'postal_code' => '08080',
'tax_id' => 'A58818501'
);
$tax = QuadernoTax::calculate($data); // Returns a QuadernoTax
$tax->name; // "VAT"
$tax->rate; // 21.0
let client = Quaderno.Client(/* ... */)
let params : [String: Any] = [
"country": "ES",
"postal_code": "08080"
"vat_number": "ESA58818501"
]
let taxCalculation = Tax.calculate(params)
client.request(taxCalculation) { response in
// response will contain the result of the request.
}
{
"name": "VAT",
"rate": 21.0,
"extra_name":null,
"extra_rate":null,
"country":"ES",
"region":null,
"county":null,
"transaction_type":"eservice",
"notes":null
}
GET
ting to /taxes/calculate.json
will calculate the applicable taxes given a customer’s data.
Parameter | Mandatory | Description |
---|---|---|
country |
Yes | Customer’s country (2-letter ISO code) |
postal_code |
No | Customer’s postal code (ZIP) |
vat_number |
No | Customer’s VAT number |
transaction_type |
No | Values: eservice , ebook , standard . Defaults to the “default tax type” configured in your account taxes settings. |
This will return a 200 OK
if the request was a success, along with the taxes represented as a JSON string.
Validating VAT Numbers
GET /taxes/validate.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X GET \
'https://ACCOUNT_NAME.quadernoapp.com/api/taxes/validate.json?country=IE&vat_number=IE6388047V'
{
"valid":true
}
country = 'IE'
vat_number = 'IE6388047V'
Quaderno::Tax.validate_vat_number(country, vat_number) #=> Boolean
$country = 'IE';
$vat_number = 'IE6388047V';
QuadernoTax::validate_vat_number($country, $vat_number) // Boolean
// Coming soon!
GET
ting to /taxes/validate.json
will validate the given EU VAT number.
Parameter | Mandatory | Description |
---|---|---|
country |
Yes | Customer’s country (2-letter ISO code) |
vat_number |
Yes | Customer’s VAT number |
Evidence
Location evidence are proofs of the customer’s location that should be stored in order to be EU VAT MOSS compliant.
The evidence object
{
"id":3649491,
"document_id":"5059bdbf2f412e0901000024",
"state":"confirmed",
"billing_country":"ES",
"ip_address":"192.168.1.1",
"ip_country":"FR",
"bank_country":"FR",
"vat_number":null,
"vies_reference":null,
"additional_evidence":null,
"additional_evidence_country":null
"notes":null
}
Attribute | Description |
---|---|
id |
Evidence ID |
document_id |
Invoice or Receipt’s ID |
state |
Customer’s location evidence state (confirmed or unconfirmed) |
billing_country |
Customer’s billing country (2-letter ISO code) |
ip_address |
Customer’s IP address |
ip_country |
Customer’s country geolocated by IP (2-letter ISO code) |
bank_country |
Customer’s bank country |
vat_number |
Customer’s intra-community VAT number (if present) |
notes |
Readable information related to the evidence state |
Create an evidence
POST /evidence.json
# body.json
{
"document_id":"5059bdbf2f412e0901000024",
"billing_country":"FR",
"ip_address":"192.168.1.1",
"bank_country":"FR"
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \ 'https://ACCOUNT_NAME.quadernoapp.com/api/evidence.json'
$evidence = new QuadernoEvidence(array(
'document_id' => '5059bdbf2f412e0901000024',
'billing_country' => 'FR',
'ip_address' => '192.168.1.1',
'bank_country' => 'FR'));
$evidence->save(); // Returns true (success) or false (error)
contact = Quaderno::Evidence.create(
document_id: '5059bdbf2f412e0901000024',
billing_country: 'FR',
ip_address: '192.168.1.1',
bank_country: 'FR')) #=> Quaderno::Evidence
// Coming soon!
POST
ing to /evidence.json
will create a new evidence from the parameters passed.
This will return 201 Created and the current JSON representation of the evidence if the creation was a success.
Attributes
Parameter | Mandatory | Description |
---|---|---|
document_id |
Yes | Invoice or Receipt’s ID |
billing_country |
No | Customer’s billing country (2-letter ISO code) |
ip_address |
No | Customer’s IP address |
bank_country |
No | Customer’s bank country (2-letter ISO code) |
Retrieve: Get and filter all evidence
GET /evidence/EVIDENCE_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/evidence.json'
// Coming soon!
# Coming soon!
// Coming soon!
[
{
"id":487,
"document_id":9756,
"state":"unsettled",
"billing_country":"US",
"ip_address":null,
"ip_country":null,
"bank_country":null,
"vat_number":null,
"vies_reference":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
},
{
"id":486,
"document_id":9755,
"state":"confirmed",
"billing_country":"ES",
"ip_address":"92.186.16.30",
"ip_country":"ES",
"bank_country":null,
"vat_number":null
"vies_reference":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
},
{
"id":485,"document_id":9752,
"state":"confirmed",
"billing_country":"ES",
"ip_address":"92.186.16.30",
"ip_country":"ES",
"bank_country":null,
"vat_number":null,
"vies_reference":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
},
{
"id":484,
"document_id":9749,
"state":"confirmed",
"billing_country":"ES",
"ip_address":"80.29.119.132",
"ip_country":"ES",
"bank_country":null,
"vat_number":null,
"vies_reference":null
,"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
},
{
"id":483,
"document_id":9748,
"state":"conflicting",
"billing_country":"ES",
"ip_address":null,
"ip_country":null,
"bank_country":"US",
"vat_number":null,
"vies_reference":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
}
]
GET
ting from /evidence.json
will return all the user’s evidence objects.
You can filter the results in a few ways:
- By state, passing the
state
parameter like?state=STATE
. You can combine multiple states separated by commas like?state=confirmed,unsettled
. Valid states areconfirmed
,unsettled
and `conflicting. - By document_id, passing the document ID in the
document_id
parameter, like?document_id=3231
.
Retrieve: Get a single evidence
GET /estimates/ESTIMATE_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/evidence/EVIDENCE_ID.json'
// Coming soon!
# Coming soon!
// Coming soon!
{
"id":483,
"document_id":9748,
"state":"conflicting",
"billing_country":"ES",
"ip_address":null,
"ip_country":null,
"bank_country":"US",
"vat_number":null,
"vies_reference":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
}
GET
ting from /evidence/EVIDENCE_ID.json
will return that specific evidence.
Webhooks
Quaderno’s webhooks allow your application to receive information about events that happen on your documents as they occur.
Data Format
{
"event_type":"invoice.created",
"data":
{
"object":
{
"id":925,
"contact_id":128,
"tag_list":[],
"number":"123346",
"issue_date":"2016-02-12",
"contact_name":"OrsonFarm",
"currency":"EUR",
"gross_amount_cents":826,
"total_cents":1000,
"amount_paid_cents":0,
"po_number":null,
"payment_details":null,
"notes":null,
"state":"outstanding",
"subject":null,
"street_line_1":null,
"street_line_2":null,
"city":null,
"postal_code":null,
"region":null,
"country":"ES",
"processor_id":null,
"processor":null,
"processor_fee_cents":null,
"custom_metadata": {
"my_custom_id": "123456"
},
"due_date":null,
"permalink":"5191567e45d6aa419927ce7a8ba2ee870c9f0a45",
"email":null,
"gross_amount":"8.26",
"total":"10.00",
"amount_paid":"0.00",
"items":
[
{
"id":1056,
"description":"Test item acces token",
"quantity":"1.0",
"unit_price":"8.26",
"discount_rate":"0.0",
"tax_1_amount_cents":174,
"tax_1_name":"IVA",
"tax_1_rate":21.0,
"tax_1_country":null,
"tax_2_amount_cents":0,
"tax_2_name":null,
"tax_2_rate":null,
"tax_2_country":null,
"subtotal_cents":"826.0",
"total_amount_cents":1000,
"discount_cents":"0.0",
"taxes_included":true,
"reference":null,
"tax_1_amount":"1.74",
"tax_2_amount":"0.00",
"unit_price_cents":"826.00",
"discount":"0.00",
"subtotal":"8.26",
"total_amount":"10.00"
}
],
"payments":[]
}
}
}
{
"event_type":"contact.updated",
"data":
{
"object":
{
"id":76,
"kind":"person",
"first_name":"Adella",
"last_name":"Schowalter",
"full_name":"Adella Schowalter",
"contact_name":null,
"street_line_1":null,
"street_line_2":null,
"postal_code":null,
"city":null,
"region":null,
"country":"DE",
"phone_1":null,
"fax":null,
"email":null,
"web":null,
"discount":null,
"language":"EN",
"tax_id":null,
"bank_account":"ES6600190020961234567890",
"notes":null,
"bic":"DEUTESBBXXX",
"vat_number":null,
"currency":"USD",
"processor_id":null,
"processor":null
}
}
}
{
"event_type":"payment.created",
"data":
{
"object":
{
"id":15,
"document_id":815,
"date":"2015-09-22",
"payment_method":"credit_card",
"amount_cents":400,
"amount":"4.00"
}
}
}
{
"event_type":"checkout.succeeded",
"data":
{
"transaction_details":
{
"gateway":"stripe",
"customer":"cus_0123456789",
"transaction":"sub_012345679",
"type":"subscription",
"plan":"awesome",
"description":"Awesome",
"amount": 99.99,
"amount_cents": 9999,
"currency":"EUR",
"tax_rate":19.0,
"tax_name":"MwSt./USt.",
"tax_country":"DE"
},
"contact":
{
"id":76,
"kind":"person",
"first_name":"Adella",
"last_name":"Schowalter",
"full_name":"Adella Schowalter",
"contact_name":null,
"street_line_1":null,
"postal_code":null,
"city":null,
"country":"DE",
"email":"adella@schowalter.com",
"language":"EN",
"tax_id":null,
"vat_number":null,
"currency":"USD"
}
}
}
{
"event_type":"checkout.failed",
"data":
{
"message":
{
"response_message": "Insufficient funds",
"status_code": 422
},
"transaction_details":
{
"gateway":"stripe",
"type":"subscription",
"plan":"awesome"
},
"contact":
{
"id":76,
"kind":"person",
"first_name":"Adella",
"last_name":"Schowalter",
"full_name":"Adella Schowalter",
"contact_name":null,
"street_line_1":null,
"postal_code":null,
"city":null,
"country":"DE",
"email":"adella@schowalter.com",
"language":"EN",
"tax_id":null,
"vat_number":null,
"currency":"USD"
}
}
}
{
"event_type":"checkout.abandoned",
"data":
{
"transaction_details":
{
"description":"Awesome",
"plan":"awesome"
},
"contact":
{
"first_name":"Adella",
"last_name":"Schowalter",
"city":null,
"country":"DE",
"email":"adella@schowalter.com"
}
}
}
Every webhook uses the same format for its data, regardless of event type. The webhooks take the form of a standard POST
, with a hash using the following paramaters:
Parameter | Description |
---|---|
event_type |
The event which triggered the webhook (invoice.created , estimate.updated , contact.deleted , etc). |
data |
A simplified JSON representation of the object. |
Event Types
Event types are a combination of the object you want to be notified about and the object state.
Available events are:
invoice.created
,invoice.updated
,invoice.deleted
receipt.created
,receipt.updated
,receipt.deleted
credit.created
,credit.updated
,credit.deleted
expense.created
,expense.updated
,expense.deleted
estimate.created
,estimate.updated
,estimate.deleted
payment.created
,payment.deleted
contact.created
,contact.updated
,contact.deleted
checkout.succeeded
,checkout.failed
,checkout.abandoned
For example, if you want to be notified whenever an invoice is created or deleted, you should subscribe to the events invoice.created
and invoice.deleted
.
Endpoint errors
If your webhook-receiving endpoint does not respond with a 200
when Quaderno POST
s the data, Quaderno will try again within 48 hours.
If you fail to respond a second time then your subscription to that webhook will be deleted.
Verifying webhook requests
Quaderno signs webhook requests so that you can (optionally) verify that requests were generated by Quaderno and not by a third-party impersonating us.
Verify request signatures
Quaderno includes an additional HTTP header with webhook POST
requests, X-Quaderno-Signature
, which will contain the signature for the request.
To verify a request, generate a signature using the same key that Quaderno uses and compare that to the value of the X-Quaderno-Signature
header.
Get your webhook authentication key
When you create a webhook a key is automatically generated. If you’re using POST /webhooks.json
the key will be returned in the response.
To retrieve a webhook key via the Quaderno API, use GET /webhooks.json
or GET /webhooks/WEBHOOK_ID.json
.
Generate a signature
/**
* Generates a base64-encoded signature for a Quaderno webhook request.
* @param string $webhook_key the webhook's authentication key
* @param string $url the webhook url
* @param array $body the request's POST parameters
*/
function generateSignature($webhook_key, $url, $body) {
$signed_data = $url . $body;
return base64_encode(hash_hmac('sha1', $signed_data, $webhook_key, true));
}
require 'openssl'
require 'base64'
# Generates a base64-encoded signature for a Quaderno webhook request.
# @param string webhook_key the webhook's authentication key
# @param string url the webhook url
# @param array body the request's POST parameters
def generateSignature(webhook_key, url, body)
signed_data = url + body
Base64.encode64(OpenSSL::HMAC.hexdigest('sha1', signed_data, webhook_key))
end
In your code that processes receieved webhooks:
- Create a string with the webhook’s URL, exactly as you entered it in Quaderno (including any query string, if applicable). Quaderno always signs webhook requests with the exact URL you provided when you configured the webhook. A difference as small as including or removing a trailing slash will prevent the signature from validating.
- Append the
POST
body to the URL string, with no delimiter. The body should be a JSON-encoded representation of the data. - Hash the resulting string with
HMAC-SHA1
, using your webhook’s authentication key to generate a binary signature. - Base64 encode the binary signature.
- Compare the binary signature that you generated to the signature provided in the
X-Quaderno-Signature
HTTP header.
Create a webhook
POST /webhooks.json
# body.json
{
"url": "http://anotherapp.com/notifications",
"events_types": [
"invoice.created",
"estimate.updated",
"invoice.deleted",
"contact.created"
]
}
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X POST \
--data-binary @body.json \
'https://ACCOUNT_NAME.quadernoapp.com/api/webhooks.json'
params = {
url: "http://anotherapp.com/notifications",
events_types: [
'invoice.created',
'estimate.updated',
'invoice.deleted',
'contact.created'
]
}
Quaderno::Webhook.create(params) #=> Quaderno::Webhook
$webhook = new QuadernoWebhook(array(
'url' => 'http://myapp.com/notifications',
'events_types' => array('contact.created'));
$webhook->save(); // Returns true (success) or false (error)
POST
ing to /webhooks.json
will create a new webhook from the passed parameters.
Mandatory fields:
Field | Description |
---|---|
url |
Indicates the destination URL of the webhook request. |
events_types |
An array of strings indicating which events you wish to subscribe to. |
Webhook URLs should be set up to accept HEAD
and POST
requests. When you provide the URL where you want Quaderno to POST
the data for events, we’ll do a quick check that the URL exists by using a HEAD
request (not POST
).
If the URL doesn’t exist or returns something other than a 200
HTTP response to the HEAD
request, Quaderno won’t be able to verify that the URL exists and is valid.
This will return 201 Created
with the current JSON representation of the webhook if the creation was a success.
Retrieve: Get all webhooks
GET /webhooks.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/ewbhooks.json'
Quaderno::Webhook.all() #=> Array
$webhooks = QuadernoWebhook::find(); // Returns an array of QuadernoWebhook
[
{
"id":2,
"url":"https://myawesomeapp.com/notifications",
"auth_key":"zXQgArTtQxAMaYppMrDoUQ",
"events":["created","updated"],
"last_sent_at":"2013-05-18T11:11:11Z",
"last_error":null,
"events_sent":null,
"created_at":"2013-05-17T14:08:05Z",
"updated_at":"2013-05-17T14:08:05Z"
}
{
"id":3,
"url":"http://anotherapp.com/notifications",
"auth_key":"HXQgAgblQxAMaNppMrXoSW",
"events:_types":["invoice.created","contact.deleted"],
"last_sent_at":null,
"last_error":null,
"events_sent":null,
"created_at":"2013-07-13T14:12:01Z",
"updated_at":"2013-07-17T14:09:59Z"
}
]
GET
ting to /webhooks.json
will return all your webhooks.
Retrieve: Get a single webhook
GET /webhooks/WEBHOOK_ID.json
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/webhooks/WEBHOOK_ID.json'
Quaderno::Webhook.find(WEBHOOK_ID) #=> Quaderno::Webhook
$webhooks = QuadernoWebhook::find(WEBHOOK_ID); // Returns a QuadernoWebhook
{
"id":3,
"url":"http://anotherapp.com/notifications",
"auth_key":"HXQgAgblQxAMaNppMrXoSW",
"events:_types":["invoice.created","contact.deleted"],
"last_sent_at":null,
"last_error":null,
"events_sent":null,
"created_at":"2013-07-13T14:12:01Z",
"updated_at":"2013-07-17T14:09:59Z"
}
GET
ting to /webhooks/WEBHOOK_ID.json
will return a specific webhook.
Update a webhook
PUT /webhooks/WEBHOOK_ID.json
curl -u YOUR_API_KEY:x \
-H 'Content-Type: application/json' \
-X PUT \
-d '{ "events_types": ["contact.updated","estimate.deleted"] }' \
'https://ACCOUNT_NAME.quadernoapp.com/api/webhooks/WEBHOOK_ID.json'
params = {
events_types: [ 'contact.updated', 'estimate.deleted' ]
}
Quaderno::Webhook.update(WEBHOOK_ID, params) #=> Quaderno::Webhook
$webhook->url = "";
$webhook->save(); // Returns false - url is a required field
foreach($webhook->errors as $field => $errors) {
print "{$field}: ";
foreach ($errors as $e) print $e;
}
$webhook->url = 'http://anotherapp.com/quaderno/notifications';
$webhook->events_types = array('contact.created', 'contact.updated', 'contact.deleted');
$webhook->save();
PUT
ting to /webhooks/WEBHOOK_ID.json
will update a webhook with the passed parameters.
This will return 201 Created
with the current JSON representation of the webhook if the update was a success.
Delete a webhook
DELETE /webhooks/WEBHOOK_ID.json
curl -u YOUR_API_KEY:x \
-X DELETE 'https://ACCOUNT_NAME.quadernoapp.com/api/webhooks/WEBHOOK_ID.json'
Quaderno::Webhook.delete(WEBHOOK_ID) #=> Boolean
DELETE
ing to /webhooks/WEBHOOK_ID.json
will delete the specified webhook and return 204 No Content
if the update was successful.
Errors
We don’t usually have any trouble on our end, but when we do we’ll let you know!
The Quaderno API uses the following error codes:
Code | Text | Description |
---|---|---|
400 |
Bad Request |
Your request may be malformed |
401 |
Unauthorized |
Your API key is wrong, or your user does not have access to this resource |
403 |
Forbidden |
The record requested is hidden for administrators only |
404 |
Not Found |
The specified record could not be found |
405 |
Method Not Allowed |
You tried to access a record with an invalid method |
406 |
Not Acceptable |
You requested a format that isn’t JSON |
410 |
Gone |
The record requested has been removed from our servers |
422 |
Unprocessable Entity |
The requested method cannot process for the record in question |
429 |
Too Many Requests |
You’re requesting too many records! Slow down! |
500 |
Internal Server Error |
We had a problem with our server. Try again later. |
502 |
Bad Gateway |
We had a different problem with our server. Try again later. |
503 |
Service Unavailable |
We’re temporarily offline for maintenance, or you’ve exceeded the rate limit. Please try again later. |
504 |
Gateway Timeout |
Yep, you guessed it. We’ll be back soon! |
Changelog
API versions and changes
20170628 (Current version)
- Return 429 (
too many requests
) instead of 503 (service unavailable
) on rate limits. - Added the
related_document
to the credit object.
20170418
- Added
custom_metadata
as an editable field.
20161108
country
is now returned as an attribute forinvoices
,receipts
,expenses
,credits
andestimates
.
20160614
- Amounts are now returned as cents instead being formatted as amount with currency symbol.
- Versions are no longer passed as part of the URL, instead they can be passed as an accept header.
20160602
- First version.
- Amounts are returned formatted as amount with currency symbol.