NAV
shell ruby php swift

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/.

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:

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:

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.
}

POSTing 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)
email 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"
  }
]

GETting 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"
}

GETting 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"
}

GETting 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.
}

PUTing 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.
}

DELETEing 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",
  "payment_processor":"stripe",
  "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
  "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',
                                 'payment_processor' => 'stripe',
                                 'payment_processor_id' => 'ch_19yUdh2eZvKYlo2CkFVBOZG7',
                                 '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',
  payment_processor: 'stripe',
  payment_processor_id: 'ch_19yUdh2eZvKYlo2CkFVBOZG7',
  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",
    "payment_processor": "stripe",
    "payment_processor_id": "ch_19yUdh2eZvKYlo2CkFVBOZG7",
    "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.
}

POSTing 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(20 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
payment_processor no Payment processor that you used to process the payment.
payment_processor_id no The id that the payment processor assigned to the payment.
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:

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",
        "payment_processor":"stripe",
        "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
        "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",
        "payment_processor":"stripe",
        "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
        "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":{}
  }
]

GETting from /receipts.json will return all the user’s receipts.

You can filter the results in a few ways:

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",
    "payment_processor":"stripe",
    "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
    "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":{}
}

GETting 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.
}

PUTting 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.
}

DELETEing 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.
}

GETting /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.
}

POSTing 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(20 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
payment_processor no Payment processor that you used to process the payment.
payment_processor_id no The id that the payment processor assigned to the payment.
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(255 chars). Recommendable to set for US sales taxes.
tax_1_city no String(255 chars). Recommendable to set for US sales taxes.
tax_1_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_1_city_code no String(255 chars). Recommendable to set for US sales 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_city no String(255 chars). Recommendable to set for US sales taxes.
tax_2_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_2_city_code no String(255 chars). Recommendable to set for US sales 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:

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",
        "payment_processor":"stripe",
        "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
        "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":{}
  }
]

GETting from /invoices.json will return all the user’s invoices.

You can filter the results in a few ways:

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":{}
}

GETting 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.
}

PUTting 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.
}

DELETEing 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.
}

GETting /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.
}

POSTing 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(20 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
payment_processor no Payment processor that you used to process the payment.
payment_processor_id no The id that the payment processor assigned to the payment.
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(255 chars). Recommendable to set for US sales taxes.
tax_1_city no String(255 chars). Recommendable to set for US sales taxes.
tax_1_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_1_city_code no String(255 chars). Recommendable to set for US sales 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(255 chars). Recommendable to set for US sales taxes.
tax_2_city no String(255 chars). Recommendable to set for US sales taxes.
tax_2_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_2_city_code no String(255 chars). Recommendable to set for US sales 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:

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",
        "payment_processor":"stripe",
        "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
        "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":{}
  }
]

GETting from /credits.json will return all the user’s credits.

You can filter the results in a few ways:

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":{}
}

GETting 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.
}

PUTting 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.
}

DELETEing to /credit/CREDIT_ID.json will delete the specified credit and returns 204 No Content if successful.

Deliver (Send) a credit

GETting /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.
}

POSTing 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(20 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
payment_processor no Payment processor that you used to process the payment.
payment_processor_id no The id that the payment processor assigned to the payment.
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_2_county no String(255 chars). Recommendable to set for US sales taxes.
tax_2_city no String(255 chars). Recommendable to set for US sales taxes.
tax_2_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_2_city_code no String(255 chars). Recommendable to set for US sales taxes.
tax_2_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(255 chars). Recommendable to set for US sales taxes.
tax_2_city no String(255 chars). Recommendable to set for US sales taxes.
tax_2_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_2_city_code no String(255 chars). Recommendable to set for US sales 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:

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":{}
  }
]

GETting from /expenses.json will return all the user’s expenses.

You can filter the results in a few ways:

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"
}

GETting 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.
}

PUTting 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"
}

GETting /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.
}

DELETEing 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.
}

POSTing 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(20 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:

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":{}
  }
]

GETting from /estimates.json will return all the user’s estimates.

You can filter the results in a few ways:

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":{}
}

GETting 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.
}

PUTting 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.
}

DELETEing 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.
}

GETting /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.
}

POSTing 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(255 chars). Recommendable to set for US sales taxes.
tax_1_city no String(255 chars). Recommendable to set for US sales taxes.
tax_1_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_1_city_code no String(255 chars). Recommendable to set for US sales 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(255 chars). Recommendable to set for US sales taxes.
tax_2_city no String(255 chars). Recommendable to set for US sales taxes.
tax_2_county_code no String(255 chars). Recommendable to set for US sales taxes.
tax_2_city_code no String(255 chars). Recommendable to set for US sales 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:

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":{}
  }
]

GETting from /recurring.json will return all the user’s recurrings.

You can filter the results in a few ways:

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":{}
}

GETting 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.
}

PUTting 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.
}

DELETEing 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:

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.
}

POSTing 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
description no String(255 chars).
currency no String(3 chars) Defaults to the account currency. ISO 4217 format
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
kind no One of the following: one_off or subscription. Default value is one_off
stripe_plan_id no String(255 chars). Only for Stripe subscriptions. Validates in Stripe that matches a real Stripe Plan Id
paypal_interval_unit no Only for Paypal subscriptions. One of the following: daily, weekly, monthly, yearly
paypal_interval_frequency no Decimal. Only for Paypal subscriptions. Frequency of the units when the charge should recur.
paypal_interval_duration no Decimal. Only for Paypal subscriptions. Number of times the charge should recur.

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",
    "kind":"one_off"
  },
  {
    "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",
    "kind":"one_off"
  },
  {
    "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",
    "kind":"one_off"
  }
]

GETting from /items.json will return all the user’s items.

You can filter the results in a few ways:

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",
  "kind":"one_off"
}

GETting 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.
}

PUTting 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.
}

DELETEing 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:

Create a payment

POST /invoices/INVOICE_ID/payments.json or POST /expenses/EXPENSE_ID/payments.json

# body.json
{
  "amount":"56.60",
  "payment_method":"credit_card",
  "payment_processor":"stripe",
  "payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7"
}

# 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',
                                'payment_processor' => 'stripe',
                                'payment_processor_id' => 'ch_19yUdh2eZvKYlo2CkFVBOZG7'));

$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',
    payment_processor: 'stripe',
    payment_processor_id: 'ch_19yUdh2eZvKYlo2CkFVBOZG7'
}
invoice.add_payment(params) #=> Quaderno::Payment
let client = Quaderno.Client(/* ... */)

let params : [String: Any] = [
 "payment_method": "credit_card",
 "amount": "56.60",
 "payment_processor": "stripe",
 "payment_processor_id": "ch_19yUdh2eZvKYlo2CkFVBOZG7"
]

let readInvoice = Invoice.read(INVOICE_ID)
client.request(readInvoice) { response in
  // response will contain the result of the request.
}

POSTing 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
payment_processor no Payment processor that you used to process the payment.
payment_processor_id no The id that the payment processor assigned to the payment.

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

GETting 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

GETting 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 or DELETE /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)

DELETEing 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=US&postal_code=94010'
params = {
    country: 'US',
    postal_code: '94010'
}
tax = Quaderno::Tax.calculate(params) #=> Quaderno::Tax
$data = array(
  'country' => 'US',
  'postal_code' => '94010'
);

$tax = QuadernoTax::calculate($data); // Returns a QuadernoTax
$tax->name; // "Sales Tax"
$tax->rate; // 9.5
let client = Quaderno.Client(/* ... */)

let params : [String: Any] = [
    "country": "US",
    "postal_code": "94010"
]

let taxCalculation = Tax.calculate(params)
client.request(taxCalculation) { response in
    // response will contain the result of the request.
}
{
    "name": "Sales tax",
    "rate": 9.5,
    "extra_name":null,
    "extra_rate":null,
    "country":"US",
    "region":"CA",
    "county":"SAN MATEO",
    "city":"BURLINGAME",
    "county_tax_code":"41",
    "city_tax_code":"746",
    "transaction_type":"eservice",
    "notes":null
}

GETting 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
city No Customer’s city (for US sales tax)
vat_number No Customer’s VAT number
transaction_type No Values: eservice, ebook, standard, saas. 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 Business 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) #=> true, false or nil

$country = 'IE';
$vat_number = 'IE6388047V';

QuadernoTax::validate_vat_number($country, $vat_number) // true, false or null
// Coming soon!

GETting to /taxes/validate.json will validate the given business number. Currently Quaderno validates EU VAT numbers, ABN, and NZBN.

Please keep in mind that business numbers are not actually validated while using the Sandbox.

Parameter Mandatory Description
country Yes Customer’s country (2-letter ISO code)
vat_number Yes Customer’s business number

This will return a 200 OK and the result of validating the business number against the official external validation service. The result can be true (valid business number), false (invalid business number) or null (the external service is temporarily unavailable).

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!

POSTing 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)
additional_evidence No (yes if additional_evidence_country is passed) An explanatory note about the additional evidence. Up to 255 chars.
additional_evidence_country No (yes if additional_evidence is passed) Additional evidence for the customer location (2-letter ISO code)

Update an evidence

PUT /evidence/EVIDENCE_ID.json

# body.json
{
  "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/EVIDENCE_ID.json'
$evidence = QuadernoEvidence::find(EVIDENCE_ID);
$evidence->ip_address = "192.168.23.23";

$evidence->save(); // Returns true (success) or false (error)
# Coming soon!

// Coming soon!

PUTting to /evidence/EVIDENCE_ID.json will update the evidence with the passed parameters.

This will return 200 OK along with the current JSON representation of the evidence if successful.

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'
$evidences = QuadernoEvidence::find(array('page'=>1))
# 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
  }
]

GETting from /evidence.json will return all the user’s evidence objects.

You can filter the results in a few ways:

Retrieve: Get a single evidence

GET /evidence/EVIDENCE_ID.json

curl -u YOUR_API_KEY:x \
     -X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/evidence/EVIDENCE_ID.json'
$evidence = QuadernoEvidence::find(EVIDENCE_ID);
# 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
  }

GETting from /evidence/EVIDENCE_ID.json will return that specific evidence.

Checkout Sessions

A Checkout Session represents your customer’s session as they pay for one-time purchases or subscriptions through Checkout.

Session object

key type description
id integer Unique identifier for the object.
billing_details_collection string The value for whether Checkout collected the customer’s billing details. Values are auto and required (default to required).
cancel_url string The URL the customer will be directed to if they decide to cancel payment and return to your website.
coupon_collection boolean The value for whether Checkout collected coupons.
custom object Set of key-value pairs that you want to forward to the payment processor. This can be useful for setting up additional options in the payment processor. If you want to send specific data to one processor, you can create a subhash with the name of that particular processor. E.g.: stripe: { metadata: { user_id: 999 } }, paypal: { no_shipping: 0 }
customer object The customer’s billing information. Check the attributes here
items array The list of products purchased by the customer. Check the item object here
locale string The 2-letter ISO code of the language the Checkout is displayed in. Values are auto, ca, de, en, es, fi, fr, hu, it, nb, nl, and sv.
metadata object Set of key-value pairs that you can attach to the session. This can be useful for storing additional information about the purchase.
payment_methods array Values are card and paypal.
permalink string The URL of this Checkout Session.
success_url string The URL the customer will be directed to after the payment is successful.
processor string Values are stripe and paypal.
processor_id string
status string pending sessions are unpaid and awaiting payment. A session is marked as failed when the payment failed or was declined. Note that this status many not show inmediately and instead show as pending until verified. completed sessions requires no further action and cannot be edited. A session is marked as abandoned after being cancelled by the customer or 30 minutes without activitiy. Values are pending, processing, failed, completed, and abandoned.

Customer object:

key type description
billing_city string City/District/Suburb/Town/Village.
billing_country string 2-letter country code.
billing_postal_code string ZIP or postal code.
billing_street_line_1 string Address line 1 (Street address/PO Box).
billing_street_line_2 string Address line 2 (Apartment/Suite/Unit/Building).
company string Customer company.
email string (email) Customer email.
first_name string Customer first name.
last_name string Customer last name.
tax_id string Customer Tax ID.
business_number string Customer Business number

Item object:

key type description
amount number(decimal) Unit price of the item being purchased.
currency string ISO 4217 currency code.
description string The description of the item being purchased.
name string The name of the item being purchased.
product string The SKU of the Quaderno Product.
quantity integer Quantity of the item being purchased.

List all sessions

You can list all sessions, or list the sessions for a specific status. The sessions are returned sorted by creation date, with the most recently created sessions appearing first.

GET /checkout/sessions.json

Returns an array of sessions. Each entry in the array is a separate session object. If no more sessions are available, the resulting array will be empty.

curl -u YOUR_API_KEY:x \
     -X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions.json'
// Coming soon!
# Coming soon!
// Coming soon!
[
   {
      "id":1,
      "status":"completed",
      "billing_details_collection":"required",
      "cancel_url":"http://go.back.com",
      "coupon_collection":true,
      "locale":"auto",
      "payment_methods":["card", "paypal"],
      "success_url":"http://success.com?prod=1",
      "custom":{},
      "items":[
         {
            "product":"prod_61ffa845b4a0b8",
            "amount":999.0,
            "name":"A test item",
            "description":"This a test item.",
            "currency":"EUR",
            "quantity":1
         }
      ],
      "customer":{
         "billing_city":"John",
         "billing_country":"GB",
         "billing_postal_code":"asd",
         "billing_street_line_1":"asd",
         "billing_street_line_2":"asd",
         "company":"",
         "email":"john@doe.com",
         "first_name":"John",
         "last_name":"Doe",
         "tax_id":null,
         "business_number":null
      },
      "permalink":"https://demo.quadernoapp.com/checkout/session/8ccf3fdc42b85800188b113b81d3e4212ef094b3"
   },
   {
      "id":2,
      "status":"failed",
      "billing_details_collection":"auto",
      "cancel_url":"http://go.back.com",
      "coupon_collection":true,
      "locale":"es",
      "payment_methods":["paypal"],
      "success_url":"http://success.com?prod=2",
      "custom":{},
      "items":[
         {
            "product":"awesome",
            "amount":999.0,
            "name":"Awesome",
            "description":"",
            "currency":"EUR",
            "quantity":1
         }
      ],
      "customer":{
         "billing_city":null,
         "billing_country":null,
         "billing_postal_code":"asd",
         "billing_street_line_1":"asdas",
         "billing_street_line_2":"asd",
         "company":"",
         "email":null,
         "first_name":"James",
         "last_name":null,
         "tax_id":null
      },
      "permalink":"https://demo.quadernoapp.com/checkout/session/5c24890be57274275358e7d25ea5200384fc7293"
   },
   {
      "id":3,
      "status":"abandoned",
      "billing_details_collection":"required",
      "success_url":"http://success.com?prod=1",
      "coupon_collection":false,
      "locale":"auto",
      "payment_methods":["card", "paypal"],
      "success_url":"http://success.com",
      "custom":{},
      "items":[
         {
            "product":"prod_61ffa845b4a0b8",
            "amount":999.0,
            "name":"Something",
            "description":null,
            "currency":"EUR",
            "quantity":1
         }
      ],
      "customer":{
         "billing_city":"York",
         "billing_country":"GB",
         "billing_postal_code":"Y01 6FA",
         "billing_street_line_1":"Fake Street 1",
         "billing_street_line_2":"Apt. 1",
         "company":"",
         "email":"giles@gorales.co.uk",
         "first_name":"Giles",
         "last_name":null,
         "tax_id":null
      },
      "permalink":"https://demo.quadernoapp.com/checkout/session/0092e2afbc457a2e3acf1a2c75928743d06c1fe5"
   }
]

Create a session

Sessions are created via links, but you can also create them via API if you need to create sessions on the fly.

POST /checkout/sessions.json

This request will return 201 Created and the current JSON representation of the session if the creation was a success.

curl -u YOUR_API_KEY:x \
     -H 'Content-Type: application/json' \
     -X POST \
     -d '{"billing_details_collection":"auto","cancel_url":"string","coupon_collection":true,"cus...}' \
     'https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions.json'
// Coming soon
# Coming soon!
// Coming soon!
key type description
billing_details_collection string The value for whether Checkout collected the customer’s billing details. Values are auto and required (default to required).
cancel_url string / required The URL the customer will be directed to if they decide to cancel payment and return to your website.
coupon_collection boolean The value for whether Checkout collected coupons.
custom object
customer object The customer’s billing information. Check the attributes here.
items array / required The list of products purchased by the customer. Check the attributes here.
locale string The 2-letter ISO code of the language the Checkout is displayed in. Values are auto, ca, de, en, es, fi, fr, hu, nl, sv, and no.
payment_methods array Values are card and paypal.
success_url string / required The URL the customer will be directed to after the payment is successful.

Customer attributes:

key type description
billing_city string City/District/Suburb/Town/Village. Use this parameter to prefill customer data if you already have her city on file.
billing_country string 2-letter country code. Use this parameter to prefill customer data if you already have her country on file.
billing_postal_code string ZIP or postal code. Use this parameter to prefill customer data if you already have her postal code on file.
billing_street_line_1 string Address line 1 (Street address/PO Box). Use this parameter to prefill customer data if you already have her street address on file.
billing_street_line_2 string Address line 2 (Apartment/Suite/Unit/Building). Use this parameter to prefill customer data if you already have her street address on file.
contact integer The ID of the Quaderno Contact. A new contact will be created unless an existing contact was provided in when the Checkout was created.
company string Customer company. Use this parameter to prefill customer data if you already have her company name on file.
email string (email) Customer email. Use this parameter to prefill customer data if you already have an email on file.
first_name string Customer first name. Use this parameter to prefill customer data if you already have her first name on file.
last_name string Customer last name. Use this parameter to prefill customer data if you already have her last name on file
tax_id string Customer Tax ID. Use this parameter to prefill customer data if you already have a tax id on file.

Items attributes:

key type description
amount number(decimal) Unit price of the item being purchased. Default value the product’s price is used if amount is not set.
currency string ISO 4217 currency code. Must be a supported currency by the payment processor. Default value the product’s currency is used if currency is not set.
description string The description of the item being purchased. Default value the product’s description is used if description is not set.
name string The name of the item being purchased. Default value the product’s name is used if name is not set.
product string / required The SKU of the Quaderno Product.
quantity integer Quantity of the item being purchased. Minimum value is 1.

Retrieve a session

Retrieves the details of an existing session. You need only supply the unique session identifier that was returned upon session creation.

GET /checkout/session/SESSION_ID.json

curl -u YOUR_API_KEY:x \
     -X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions/SESSION_ID.json'
# Coming soon!
// Coming soon!

// Coming soon!
{
      "id":1,
      "status":"completed",
      "billing_details_collection":"required",
      "cancel_url":"http://go.back.com",
      "coupon_collection":true,
      "locale":"auto",
      "payment_methods":["card", "paypal"],
      "success_url":"http://success.com?prod=1",
      "custom":{},
      "items":[
         {
            "product":"prod_61ffa845b4a0b8",
            "amount":999.0,
            "name":"A test item",
            "description":"This a test item.",
            "currency":"EUR",
            "quantity":1
         }
      ],
      "customer":{
         "billing_city":"John",
         "billing_country":"GB",
         "billing_postal_code":"asd",
         "billing_street_line_1":"asd",
         "billing_street_line_2":"asd",
         "company":"",
         "email":"john@doe.com",
         "first_name":"John",
         "last_name":"Doe",
         "tax_id":null,
         "business_number":null
      },
      "permalink":"https://demo.quadernoapp.com/checkout/session/8ccf3fdc42b85800188b113b81d3e4212ef094b3"
   }

Update a session

Updates the specified session by setting the values of the parameters passed. Any parameters not provided will be left unchanged. Only pending sessions can be updated.

This request accepts mostly the same arguments as the session creation call.

PUT /checkout/session/SESSION_ID.json

This will return 200 OK and a JSON representation of the contact if successful.

curl \
 -X PUT https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions/SESSION_ID \
 -H "Content-Type: application/json" \
 -d '{"id":42,"billing_details_collection":"auto","cancel_url":"htttp://cancel.url.com","coupon_collection":true,"loc...}'
# Coming soon!
// Coming soon!

// Coming soon!
key type description
id integer Unique identifier for the object.
billing_details_collection string The value for whether Checkout collected the customer’s billing details. Values are auto and required (default to required).
cancel_url string / required The URL the customer will be directed to if they decide to cancel payment and return to your website.
coupon_collection boolean The value for whether Checkout collected coupons.
custom object
customer object The customer’s billing information. Check the attributes here.
items array / required The list of products purchased by the customer. Check the attributes here.
locale string The 2-letter ISO code of the language the Checkout is displayed in. Values are auto, ca, de, en, es, fi, fr, hu, nl, sv, and no.
payment_methods array Values are card and paypal.
success_url string / required The URL the customer will be directed to after the payment is successful.

Customer attributes:

key type description
billing_city string City/District/Suburb/Town/Village. Use this parameter to prefill customer data if you already have her city on file.
billing_country string 2-letter country code. Use this parameter to prefill customer data if you already have her country on file.
billing_postal_code string ZIP or postal code. Use this parameter to prefill customer data if you already have her postal code on file.
billing_street_line_1 string Address line 1 (Street address/PO Box). Use this parameter to prefill customer data if you already have her street address on file.
billing_street_line_2 string Address line 2 (Apartment/Suite/Unit/Building). Use this parameter to prefill customer data if you already have her street address on file.
contact integer The ID of the Quaderno Contact. A new contact will be created unless an existing contact was provided in when the Checkout was created.
company string Customer company. Use this parameter to prefill customer data if you already have her company name on file.
email string (email) Customer email. Use this parameter to prefill customer data if you already have an email on file.
first_name string Customer first name. Use this parameter to prefill customer data if you already have her first name on file.
last_name string Customer last name. Use this parameter to prefill customer data if you already have her last name on file
tax_id string Customer Tax ID. Use this parameter to prefill customer data if you already have a tax id on file.

Items attributes:

key type description
amount number(decimal) Unit price of the item being purchased. Default value the product’s price is used if amount is not set.
currency string ISO 4217 currency code. Must be a supported currency by the payment processor. Default value the product’s currency is used if currency is not set.
description string The description of the item being purchased. Default value the product’s description is used if description is not set.
name string The name of the item being purchased. Default value the product’s name is used if name is not set.
product string / required The SKU of the Quaderno Product.

Delete a session

Permanently deletes a session. It cannot be undone. Only pending sessions can be deleted.

DELETE /checkout/sessions/SESSION_ID.json

Will delete the specified contact and returns 204 No Content if successful.

URL parameters:

key type description
id integer / required The ID of the session to be deleted.
$ curl \
 -X DELETE https://quadernoapp.com/api/checkout/sessions/{id} \
 -H "Content-Type: application/json"
# Coming soon!
// Coming soon!
// Coming soon!

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":{
    "object":{
      "transaction_details":{
        "session":43,
        "session_permalink":"https://demo.quadernoapp.com/checkout/session/8ccf3fdc42b85800188b113b81d3e4212ef094b3",
        "gateway":"stripe",
        "type":"charge",
        "description":"Unicorn",
        "customer":"cus_FXesSyaK3CG8Oz",
        "email":"john@doe.com",
        "transaction":"pi_1F2ZYtEjVHvINKlcq2as8H5V",
        "product_id":"prod_61ffa845b4a0b8",
        "tax_name":"VAT",
        "tax_rate":20.0,
        "extra_tax_name":null,
        "extra_tax_rate":null,
        "iat":1564647784,
        "amount_cents":1500,
        "amount":"15.00",
        "currency":"EUR"
      },
      "contact":{
        "id":547540,
        "kind":"company",
        "first_name":"John ",
        "last_name":"Doe",
        "full_name":"John  Doe",
        "contact_name":null,
        "street_line_1":"Fake Street 1",
        "postal_code":"SW15 5PU",
        "city":null,
        "region":null,
        "country":"GB",
        "email":"john@doe.com",
        "web":null,
        "language":"EN",
        "tax_id":null,
        "vat_number":null
      }
    }
 }
}

{
  "event_type":"checkout.failed",
  "data":{
    "object":{
      "message":{
        "response_message":"Insufficient funds",
        "status_code":422
      },
      "transaction_details":{
        "gateway":"stripe",
        "type":"charge",
        "description":"Unicorn",
      },
      "contact":{
        "id":547540,
        "kind":"company",
        "first_name":"John ",
        "last_name":"Doe",
        "full_name":"John  Doe",
        "contact_name":null,
        "street_line_1":"Fake Street 1",
        "postal_code":"SW15 5PU",
        "city":null,
        "region":null,
        "country":"GB",
        "email":"john@doe.com",
        "web":null,
        "language":"EN",
        "tax_id":null,
        "vat_number":null
      }
    }
  }
}

{
  "event_type":"checkout.abandoned",
  "data":{
    "object":{
      "transaction_details":{
        "description":"Unicorn",
        "plan":"awesome"
      },
      "contact":{
        "first_name":"John ",
        "last_name":"Doe",
        "city":null,
        "country":"GB",
        "email":"john@doe.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:

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 POSTs 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.digest('sha1', webhook_key, signed_data))
end

In your code that processes receieved webhooks:

  1. 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.
  2. Append the POST body to the URL string, with no delimiter. The body should be a JSON-encoded representation of the data.
  3. Hash the resulting string with HMAC-SHA1, using your webhook’s authentication key to generate a binary signature.
  4. Base64 encode the binary signature.
  5. 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)

POSTing 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"
  }
]

GETting 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"
}

GETting 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();

PUTting 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

DELETEing 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

20170914 (Current version)

20170628

20170418

20161108

20160614

20160602