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
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",
  "contact_id":"5059bdbf2f412e0901000024",
  "contact_name":"STARK",
  "po_number":"",
  "currency":"USD",
  "tag_list":"playboy, businessman",
  "items_attributes":[
    {
      "description":"Whiskey",
      "quantity":"1.0",
      "unit_price":"20.0",
      "discount_rate":"0.0",
      "reference":"item_code_X"
    }
  ],
  "custom_metadata":{
    "a_custom_key":"a custom value"
  }
}

curl -u YOUR_API_KEY:x \
     -H 'Content-Type: application/json' \
     -X POST \
     --data-binary @body.json \
     'https://ACCOUNT_NAME.quadernoapp.com/api/receipts.json'
$receipt = new QuadernoReceipt(array(
                                 'contact_id' => '5059bdbf2f412e0901000024',
                                 'payment_method' => 'credit_card',
                                 'po_number' => '',
                                 'currency' => 'USD',
                                 'tag_list' => array('playboy', 'businessman'),
                                 'custom_metadata' => array('a_custom_key' => 'a custom value')));
$item = new QuadernoDocumentItem(array(
                               'description' => 'Pizza bagels',
                               'unit_price' => 9.99,
                               'quantity' => 20));
$contact = QuadernoContact::find('5059bdbf2f412e0901000024');

$receipt->addItem($item);
$receipt->addContact($contact);

$receipt->save(); // Returns true (success) or false (error)
contact = Quaderno::Contact.find('50603e722f412e0435000024') #=> Quaderno::Contact

params = {
  contact_id: contact.id,
  po_number: '',
  payment_method: 'credit_card',
  currency: 'USD',
  tag_list: ['playboy', 'businessman'],
  items_attributes: [
    {
      description: 'Whiskey',
      quantity: '1.0',
      unit_price: '20.0',
      discount_rate: '0.0'
    }
  ],
  custom_metadata: {
    a_custom_key: 'a custom value'
  }
}
receipt = Quaderno::Receipt.create(params) #=> Quaderno::Receipt
let client = Quaderno.Client(/* ... */)

let params : [String: Any] = [
    "payment_method":"credit_card",
    "contact_id":"5059bdbf2f412e0901000024",
    "contact_name":"STARK",
    "po_number":"",
    "currency":"USD",
    "tag_list":"playboy, businessman"
]

let createReceipt = Receipt.create(params)
client.request(createReceipt) { response in
    // response will contain the result of the request.
}

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(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness
issue_date no String(255 chars) Available for all except for recurring. Defaults to the current date. Format YYYY-MM-DD
po_number no String(255 chars)
due_date no String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD
currency no String(3 chars) Defaults to the account currency. En formato ISO 4217
tag_list no String(255 chars). Multiple tags should be separated by commas
payment_details no Text
notes no Text
contact_id (Mandatory if contact is not present) ID
contact (Mandatory if contact_id is not present) Hash with a contact data for creation
street_line_1 no String(255 chars). Available for updates
street_line_2 no String(255 chars)
city no String(255 chars). Available for updates
region no String(255 chars). Available for updates
postal_code no String(255 chars). Available for updates
items_attributes yes Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items.
payment_method yes One of the following: credit_card, cash, wire_transfer, direct_debit, check, promissory_note, iou, paypal or other
custom_metadata no Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long.

Document Item Attributes

Attribute Mandatory Type/Description
id no ID. Available only for updates
description yes String(255 chars)
quantity no Decimal. Defaults to 1.0
unit_price yes Decimal
total_amount Mandatory if unit_price is not present Decimal
discount_rate no Decimal
tax_1_name Mandatory if tax_1_rate is present String(255 chars)
tax_1_rate Mandatory if tax_1_name is present Decimal between -100.00 and 100.00 (not included)
tax_1_country no String(2 chars). Defaults to the contact’s country
tax_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
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",
        "amount_cents":"9375",
      },
    ],
    "notes":"",
    "state":"paid",
    "tag_list":["lasagna", "cat"],
    "secure_id":"7hef1rs7p3rm4l1nk",
    "permalink":"https://quadernoapp.com/receipt/7hef1rs7p3rm4l1nk",
    "pdf":"https://quadernoapp.com/receipt/7hef1rs7p3rm4l1nk.pdf",
    "url":"https://ACCOUNT_NAME.quadernoapp.com/api/receipts/507693322f412e0e2e00000f.json",
    "custom_metadata":{}
  },

  {
    "id":"507693322f412e0e2e0000da",
    "number":"0000047",
    "issue_date":"2012-10-11",
    "created_at":"1325376020",
    "contact":{
      "id":"5073f9c22f412e02d00004cf",
      "full_name":"Teenage Mutant Ninja Turtles"
    },
    "country":"US",
    "street_line_1":"Melrose Ave, Sewer #3",
    "street_line_2":"",
    "city":"New York",
    "region":"New York",
    "postal_code":"",
    "po_number":null,
    "currency":"USD",
    "exchange_rate":"0.680309",
    "items":[
      {
        "id":"481516234291",
        "description":"pizza",
        "quantity":"1.0",
        "unit_price_cents":"6000",
        "discount_rate":"0",
        "tax_1_name":"",
        "tax_1_rate":"",
        "tax_2_name":"",
        "tax_2_rate":"",
        "reference":"Even the bad ones taste good!",
        "subtotal_cents":"6000",
        "discount_cents":"0",
        "gross_amount_cents":"6000"
      }
    ],
    "subtotal_cents":"6000",
    "discount_cents":"0",
    "taxes":[],
    "total_cents":"6000",
    "payments":[
      {
        "id":"50aca7d92f412eda5200002ssdc",
        "date":"2012-11-21",
        "payment_method":"credit_card",
        "amount_cents":"6000",
      },
    ],
    "notes":"",
    "state":"outstanding",
    "tag_list":["pizza", "turtles"],
    "secure_id":"7hes3c0ndp3rm4l1nk",
    "permalink":"https://ACCOUNT_NAME.quadernoapp.com/receipt/7hes3c0ndp3rm4l1nk",
    "pdf":"https://ACCOUNT_NAME.quadernoapp.com/receipt/7hes3c0ndp3rm4l1nk.pdf",
    "url":"https://ACCOUNT_NAME.quadernoapp.com/api/receipts/507693322f412e0e2e0000da.json",
    "custom_metadata":{}
  }
]

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",
    "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(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness
issue_date no String(255 chars) Available for all except for recurring. Defaults to the current date. Format YYYY-MM-DD
po_number no String(255 chars)
due_date no String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD
currency no String(3 chars) Defaults to the account currency. En formato ISO 4217
tag_list no String(255 chars). Multiple tags should be separated by commas
payment_details no Text
notes no Text
contact_id (Mandatory if contact is not present) ID
contact (Mandatory if contact_id is not present) Hash with a contact data for creation
country no String(2 chars) ISO 3166-1 alpha-2. Available for updates
street_line_1 no String(255 chars). Available for updates
street_line_2 no String(255 chars)
city no String(255 chars). Available for updates
region no String(255 chars). Available for updates
postal_code no String(255 chars). Available for updates
items_attributes yes Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items.
payment_method no Create a paid document in a single request. One of the following: credit_card, cash, wire_transfer, direct_debit, check, promissory_note, iou, paypal or other
custom_metadata no Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long.

Document Item Attributes

Attribute Mandatory Type/Description
id no ID. Available only for updates
description yes String(255 chars)
quantity no Decimal. Defaults to 1.0
unit_price yes Decimal
total_amount Mandatory if unit_price is not present Decimal
discount_rate no Decimal
tax_1_name Mandatory if tax_1_rate is present String(255 chars)
tax_1_rate Mandatory if tax_1_name is present Decimal between -100.00 and 100.00 (not included)
tax_1_country no String(2 chars). Defaults to the contact’s country
tax_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
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",
        "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
number no String(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness
issue_date no String(255 chars) Available for all except for recurring. Defaults to the current date. Format YYYY-MM-DD
po_number no String(255 chars)
due_date no String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD
currency no String(3 chars) Defaults to the account currency. En formato ISO 4217
tag_list no String(255 chars). Multiple tags should be separated by commas
payment_details no Text
notes no Text
contact_id (Mandatory if contact is not present) ID
contact (Mandatory if contact_id is not present) Hash with a contact data for creation
country no String(2 chars) ISO 3166-1 alpha-2. Available for updates
street_line_1 no String(255 chars). Available for updates
street_line_2 no String(255 chars)
city no String(255 chars). Available for updates
region no String(255 chars). Available for updates
postal_code no String(255 chars). Available for updates
items_attributes yes Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items.
payment_method no Create a paid document in a single request. One of the following: credit_card, cash, wire_transfer, direct_debit, check, promissory_note, iou, paypal or other
custom_metadata no Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long.

Document Item Attributes

Attribute Mandatory Type/Description
id no ID. Available only for updates
description yes String(255 chars)
quantity no Decimal. Defaults to 1.0
unit_price yes Decimal
total_amount Mandatory if unit_price is not present Decimal. Cents version available as total_amount_cents
discount_rate no Decimal
tax_1_name Mandatory if tax_1_rate is present String(255 chars)
tax_1_rate Mandatory if tax_1_name is present Decimal between -100.00 and 100.00 (not included)
tax_1_country no String(2 chars). Defaults to the contact’s country
tax_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
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

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",
        "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(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness
issue_date no String(255 chars) Available for all except for recurring. Defaults to the current date. Format YYYY-MM-DD
po_number no String(255 chars)
due_date no String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD
currency no String(3 chars) Defaults to the account currency. En formato ISO 4217
tag_list no String(255 chars). Multiple tags should be separated by commas
payment_details no Text
notes no Text
contact_id (Mandatory if contact is not present) ID
contact (Mandatory if contact_id is not present) Hash with a contact data for creation
country no String(2 chars) ISO 3166-1 alpha-2. Available for updates
street_line_1 no String(255 chars). Available for updates
street_line_2 no String(255 chars)
city no String(255 chars). Available for updates
region no String(255 chars). Available for updates
postal_code no String(255 chars). Available for updates
items_attributes yes Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests. Maximum items per document are limited up to 1000 items.
custom_metadata no Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long.

Document Item Attributes

Attribute Mandatory Type/Description
id no ID. Available only for updates
description yes String(255 chars)
quantity no Decimal. Defaults to 1.0
unit_price yes Decimal
total_amount Mandatory if unit_price is not present Decimal
discount_rate no Decimal
tax_1_name Mandatory if tax_1_rate is present String(255 chars)
tax_1_rate Mandatory if tax_1_name is present Decimal between -100.00 and 100.00 (not included)
tax_1_country no String(2 chars). Defaults to the contact’s country
tax_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
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(255 chars) Available for invoices, credit_notes, receipts and estimates. Automatic numbering is used by default. Validates uniqueness
issue_date no String(255 chars) Available for all except for recurring. Defaults to the current date. Format YYYY-MM-DD
po_number no String(255 chars)
due_date no String(255 chars) Available for invoices, expenses and credit notes. Format YYYY-MM-DD
currency no String(3 chars) Defaults to the account currency. En formato ISO 4217
tag_list no String(255 chars). Multiple tags should be separated by commas
payment_details no Text
notes no Text
contact_id (Mandatory if contact is not present) ID
contact (Mandatory if contact_id is not present) Hash with a contact data for creation
country no String(2 chars) ISO 3166-1 alpha-2. Available for updates
street_line_1 no String(255 chars). Available for updates
street_line_2 no String(255 chars)
city no String(255 chars). Available for updates
region no String(255 chars). Available for updates
postal_code no String(255 chars). Available for updates
items_attributes yes Array of document items (check available attributes for document items below). No more than 200 items are allowed in a request. To add more use subsequent update requests
custom_metadata no Key-value data. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long.

Document Item Attributes

Attribute Mandatory Type/Description
id no ID. Available only for updates
description yes String(255 chars)
quantity no Decimal. Defaults to 1.0
unit_price yes Decimal
total_amount Mandatory if unit_price is not present Decimal. Cents version available as total_amount_cents
discount_rate no Decimal
tax_1_name Mandatory if tax_1_rate is present String(255 chars)
tax_1_rate Mandatory if tax_1_name is present Decimal between -100.00 and 100.00 (not included)
tax_1_country no String(2 chars). Defaults to the contact’s country
tax_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
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_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
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",
    "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",
    "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",
  "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
tax_1_name Mandatory if tax_1_rate and tax_1_country are present)
tax_1_rate Mandatory if tax_1_name and tax_1_country are present) Decimal between -100.00 and 100.00 (not incluided)
tax_1_country Mandatory if tax_1_name and tax_1_rate are present) String(2 chars). Format ISO 3166-1 alpha-2
tax_2_name Mandatory if tax_2_rate and tax_2_country are present)
tax_2_rate Mandatory if tax_2_name and tax_2_country are present) Decimal between -100.00 and 100.00 (not incluided)
tax_2_country Mandatory if tax_2_name and tax_2_rate are present) String(2 chars). Format ISO 3166-1 alpha-2
stock no Decimal

Retrieve: Get and filter all items

GET /items.json

curl -u YOUR_API_KEY:x \
     -X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/items.json'
Quaderno::Item.all() #=> Array
$items = QuadernoItem::find(); // Returns an array of QuadernoItem
let client = Quaderno.Client(/* ... */)

let readItem = Item.list(pageNum)
client.request(readItem) { response in
  // response will contain the result of the request.
}
[
  {
    "id":1,
    "code":"BluRay 0000",
    "name":"Titanic",
    "unit_cost":"15.0",
    "stock":"100",
    "url":"https://my-account.quadernoapp.com/api/items/1"
  },
  {
    "id":2,
    "code":"BluRay 0001",
    "name":"Titanic II: The revenge",
    "unit_cost":"15.0",
    "tax_1_name":"AWESOME_TAX",
    "tax_1_rate":"7.00",
    "url":"https://my-account.quadernoapp.com/api/items/2"
  },
  {
    "id":3,
    "code":"BluRay 0002",
    "name":"Titanic III: The origin",
    "unit_cost":"15.0",
    "stock":"33",
    "url":"https://my-account.quadernoapp.com/api/items/3"
  }
]

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

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

# curl command
curl -u YOUR_API_KEY:x \
     -H 'Content-Type: application/json' \
     -X POST \
     --data-binary @body.json \
     'https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/payments.json'
$payment = new QuadernoPayment(array(
                                'date' => date('2012-10-10'),
                                'payment_method' => 'credit_card'),
                                'amount' => '56.60');

$invoice->addPayment($payment);               // Return true (success) or false (error)
$invoice->save();                             // Returns true (success) or false (error)
invoice = Quaderno::Invoice.find(invoice_id)
params = {
    payment_method: 'credit_card',
    amount: '56.60'
}
invoice.add_payment(params) #=> Quaderno::Payment
let client = Quaderno.Client(/* ... */)

let params : [String: Any] = [
 "payment_method": "credit_card",
 "amount": "56.60"
]

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

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

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=ES&postal_code=08080&vat_number=ESA58818501'
params = {
    country: 'ES',
    postal_code: '08080'
    vat_number: 'ESA58818501'
}
tax = Quaderno::Tax.calculate(params) #=> Quaderno::Tax
$data = array(
  'country' => 'ES',
  'postal_code' => '08080',
  'tax_id' => 'A58818501'
);

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

let params : [String: Any] = [
    "country": "ES",
    "postal_code": "08080"
    "vat_number": "ESA58818501"
]

let taxCalculation = Tax.calculate(params)
client.request(taxCalculation) { response in
    // response will contain the result of the request.
}
{
    "name": "VAT",
    "rate": 21.0,
    "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)
vat_number No Customer’s VAT number
transaction_type No Values: eservice, ebook, standard. Default is eservice

This will return a 200 OK if the request was a success, along with the taxes represented as a JSON string.

Validating VAT Numbers

GET /taxes/validate.json

curl -u YOUR_API_KEY:x \
     -H 'Content-Type: application/json' \
     -X GET \
     'https://ACCOUNT_NAME.quadernoapp.com/api/taxes/validate.json?country=IE&vat_number=IE6388047V'

{
    "valid":true
}
country = 'IE'
vat_number = 'IE6388047V'

Quaderno::Tax.validate_vat_number(country, vat_number) #=> Boolean

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

QuadernoTax::validate_vat_number($country, $vat_number) // Boolean
// Coming soon!

GETting to /taxes/validate.json will validate the given EU VAT number.

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

Evidences

Location evidences 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,
  "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 /evidences.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/evidences.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 /evidences.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)

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

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 objects at present are:

And the following states are supported:

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

20170628 (Current version)

20170418

20161108

20160614

20160602