Quaderno API Features
Use this Base URL for production:
https://{{ACCOUNT_NAME}}.quadernoapp.com/api
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.
Use this Base URL for Quaderno Sandbox testing:
https://{{ACCOUNT_NAME}}.sandbox-quadernoapp.com/api
If you have any suggestions, tips, or questions that you feel aren't answered here or in our developer site, please get in touch and let us know!
Authentication
To authorize, use this code:
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices.json \
-u YOUR_API_KEY:x
You can also
ping
to check if the service is up, your credentials are correct, or to know your remaining requests without doing an actual request:
curl https://quadernoapp.com/api/ping \
-u YOUR_API_KEY:x
Quaderno uses an API key to authorise all requests, allowing access to the API in combination with the account name in the endpoint URL. You can find your API keys in quadernoapp.com/users/api-keys.
Quaderno expects the API key to be included via HTTP Basic Auth in all API requests to the server. When using curl, it looks like the following:
-u YOUR_API_KEY:x
Using that modifier on curl will send a request with an HTTP header like Authorization: Basic xxxx
Authorization
curl https://quadernoapp.com/api/authorization \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"identity": {
"id": "999",
"name": "Sheldon Cooper",
"email": "s.cooperphd@yahoo.com",
"publishable_key":"pk_1111111111111111",
"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:
Parameter | 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. |
publishable_key |
The users's publishable key. |
href |
The custom API endpoint URL for the user, providing the sought-after ACCOUNT_NAME between http:// and .quadernoapp... . |
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
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
A call with the
created_before
parameter set:
curl https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json?created_before=42 \
-u YOUR_API_KEY:x
Pagination is performed with a created_before
parameter. This parameter takes an existing object ID value and returns objects listed after the named object, in reverse chronological order.
The HTTP header X-Pages-HasMore
indicates whether more records can be fetched by using the same query with a lower created_before
.
The HTTP header X-Pages-NextPage
contains the URL that should be used to fetch the next page of records. It is only present if more records exist.
Bear in mind that Quaderno paginates GET
index results.
You can change the number of objects to be returned with the limit
parameter, defaulting to 25
. This value is capped at 100 objects.
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! |
API Versioning
The Quaderno API is versioned. Our API versions are named for the date the version is released, like 20210701
.
A new API version is released when we introduce a backwards-incompatible change to the API. For example, changing a field type or name, or deprecating endpoints.
Backwards-incompatible (also called breaking changes) example:
// Prior to version 20210316 the tax calculation endpoint was called like:
/taxes/calculate?country=US&postal_code=94010
// From version 20210316 taxes are calculated with:
/tax_rates/calculate?to_country=US&to_postal_code=90210&tax_code=standard&amount=10
The worldwide tax panorama changes fast, and so we adapt. While we're adding functionality to our API, we won't release a new API version. You'll be able to take advantage of this non-breaking backwards-compatible changes directly on the API version you're currently using.
Quaderno considers the following changes to be backwards-compatible:
- Adding new API resources.
- Adding new optional request parameters to existing API methods.
- Adding new properties to existing API responses.
- Changing the order of properties in existing API responses.
- Changing the length or format of opaque strings, such as object IDs, error messages, and other human-readable strings.
- Adding new event types.
Your integration should gracefully handle backwards-compatible changes.
Upgrading your API version
You programatically override the account's default API version with an
Accept: api_version=version_number
header.
curl https://ACCOUNT_NAME.quadernoapp.com/api/contacts.json?created_before=2048 \
-u YOUR_API_KEY:x \
-H 'Accept: application/json; api_version=20160602'
If you’re running an older version of the API, upgrade to the latest version to take advantage of new functionality or to streamline responses so the API is faster for you.
All changes are published on the API Changelog.
Upgrading your API version might break your current integration so we recommend testing on Quaderno Sandbox first.
To make the API upgrade definitive or see what version you’re running, visit your Profile → API Keys.
Version sunsetting and backported changes
From time to time, we will sunset old API versions. All Quaderno accounts using that version will be notified with at least one month in advance. Our own Engineering team will provide full support during transition.
Exceptionally, we might be forced to backport backwards-incompatible changes to all API versions for legal or security reasons, as well as for hotfixes needed to adapt to 3rd parties changes. This is the case of the removal of the DELETE /invoices/INVOICE_ID
endpoint last summer. These changes receive the same treatment as sunsetting API versions.
Stay informed
To get important announcements regarding version sunsetting, backported changes, and new API versions, please ask you account admin to add a developer role to their account with a valid email so that API announcements reach its audience.
You can also check our What's New being informed of new features and improvements added to the app, and developer and integrations news.
Safely upgrading to API version 20220325
In order to avoid confusion with the variable with the confusing explanations of the notice
field in the /tax_rates/calculate
response, we've revamped the calculator internally to track the calculation process and return the reasoning behind a result a status
code. If you were using the notice
in your API automations, you'll need to swap to the status
field as notice
is no longer returned.
Safely upgrading to API version 20210701
In our goal to make Quaderno compliant with tax rules worldwide, we're going to limit the edition of invoices and credit notes via API as of 1 July.
From that date, an invoice is final (not editable) when any of the following occur:
- The invoice has been sent to the customer. All sent invoices are final and cannot be edited.
- The invoice has been paid. All paid invoices are final and cannot be edited.
- The invoice has been manually marked as final in the dashboard.
If you need to make any changes to invoice's items, taxes, issue date or tax ID, you'll need to issue a credit note and create a new invoice.
You will still be allowed to edit the customer's billing address, PO number, tags, payment details, additional notes, and custom metadata.
What this breaking change implies:
DELETE /invoices/INVOICE_ID
will cease to exist and return a410 Gone
.PUT /invoices/INVOICE_ID
will only allow modification of the parameterspo_number
,tag_list
,payment_details
,notes
,custom_metadata
and all those related to the billing address:street_line_1
,street_line_2
,city
,region
,andpostal_code
.
Note that on 1 July the transition period for 20210316 ends as well, and the legacy endpoints for taxes and pagination will also respond 410 Gone
.
If you're using these endpoints, you'll have to include extra logic in your code to issue a credit note and create a new invoice.
However, given your use case is one that automatically issues paid invoices (for instance, your customer's pays and then your system issues the invoice, not the other way around) and you're up for a challenge, our recommendation to face this change is to migrate to the new Transactions API.
Why? Because you can create the contact (if doesn't exists yet), create the invoice, and insert the payment information, all in only one performant API call 🎉!
Safely upgrading to API version 20210316
Most changes simply add functionality, thus maintaining backwards compatibility. Occasionally, we release a version with breaking changes, such as the current 20210316.
Example of the new Tax Rates endpoint
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/calculate?to_country=US&to_postal_code=90210&tax_code=standard&amount=10 \
-u YOUR_API_KEY:x
Note you'll get a 0.0
rate
along with thisnotice
in the response for both cases, unless you are registered on the tax jurisdiction:
"notice": "You haven't registered this tax jurisdiction in your Quaderno account. If you have to collect taxes here, please set it up in https://ACCOUNT_NAME.quadernoapp.com/settings/jurisdictions ."
Do not fret, the transition to 20210316 is as easy as pie 🥧.
For calculating tax rates, altough the new endpoint accepts many more parameters, just changing the URL from /taxes/calculate
to /tax_rates/calculate
will do. Well, not so fast. It may seem it worked quickly, but you're probably getting a 0% tax rate. That's because now you can only collect taxes on Tax Jurisdictions where you're registered. So don't be tempted to create a new custom Tax Rate, just head to your Quaderno Account and update the tax jurisdictions where your business is registered in. Now we're talking! Quaderno will always provide updated worldwide tax rates seamlessly for you.
Alternatively, you can register jurisdictions programatically, first listing all jurisdictions and then registering your Tax IDs on the jurisdictions. You can read more about Tax Jurisdictions here.
For paginating objects, we switched to a faster cursor based pagination approach and you'll need to change the page
parameter for created_before
, which references the ID of the object instead of the page number. You'll get a maximum of 100 objects in reverse chronological order (lower IDs means older objects). Note the response headers changed from X-Pages-CurrentPage
and X-Pages-TotalPages
to X-Pages-HasMore
and X-Pages-NextPage
.
You can test all this changes in our sandbox. There are two approaches:
- Simply change the API version globally in Profile → API Keys. Note once you change it you cannot change it back, but you can override a concrete call sending the
Accept
HTTP header with:Accept: application/json; api_version=20170914
- Sending the
Accept
HTTP header with:Accept: application/json; api_version=20210316
on every call.
And that's basically it 🎉! Now you can go focus on your business while we deal with your taxes.
Changelog
API versions and changes
20220325 (Current version)
- Breaking change:
/tax_rates/calculate
no longer returns anotice
explanation text with the tax calculation reason. - Backwards-compatible change:
/tax_rates/calculate
now always show astatus
on the response indicating a code for the calculation scenario.
20210701
- Backwards-compatible change: Added
verification_code
andverification_permalink
to invoices and credits. - Backported breaking change: Maximum items per document are now limited up to 200. Before, this was a soft limit per API request, which could be workarounded with subsequent PUT calls.
- Backwards-compatible change: Billing API now supports partial refunds with the parameter
credited_amount
for invoices with one single item. - Backwards-compatible change: passing
none
as theregion
parameter to theGET /jurisdictions
endpoint returns jurisdictions that do not have a specific region. - Backwards-compatible change: new
threshold.eu.100k
webhook for the Annual turnover report, to warn whenever sales of digital services within the EU (exclusive of VAT) reach €100,000. Applies only to EU-based sellers. - Backported breaking change:
abandoned
checkout sessions are automatically removed after seven days of inactivity (this is, no updates or not submission attempts from the customer). - Backwards-compatible change: we're adding new webhooks for the new Annual turnover report:
threshold.warning
andthreshold.exceeded
. See an example of the payload here. - Backported breaking change: we're removing the ability to edit coupons linked to Stripe coupons, as they won't allow edition and led to inconsistent states.
- Backported breaking change: to complain with tax rules worldwide,
DELETE /invoices/INVOICE_ID
andDELETE /credits/CREDIT_ID
endpoints no longer exists and return404 Not Found
. Instead deleting invoices you should associate them to credit notes usingPOST /credits
with the invoice id, which will change its state topaid
instead therefunded
state. To correct errors creating credits we also provide aPUT /credits/CREDIT_ID/void
to void credit notes. Note the webhooksinvoice.deleted
andcredit.deleted
are also gone. - Backported breaking change: to complain with tax rules worldwide,
PUT /invoices/INVOICE_ID
andPUT /credits/CREDIT_ID
only allow modification of the parametersnotes
,tag_list
,custom_metadata
and all those related to the customer's address:street_line_1
,street_line_2
,city
,region
,postal_code
andcountry
. This change applies to ALL versions. - Backwards-compatible change:
/tax_rates/calculate
now always show aproduct_type
on the response. When you specified theproduct_type
explicitly on the request, we've been showing it in the response. However when you didn't, we used the account's default for tax calculation, and did not return that field. Now we always return the field used for the tax calculation in the response, whether you made it explicit on the request, or the account's default is used. It's a more consistent and informative behaviour. - Backwards-compatible change: creating and updating Connect custom accounts (POST and PUT to
/accounts
endpoint) now accepts specifying thedefault_product_type
anddefault_tax_code
. Before, defaults ofservice
andeservice
were applied.
20210316
- Breaking change: record pagination is no longer performed with a
page
parameter, but with acreated_before
parameter. See the pagination documentation for more details. - Breaking change: the existing /taxes/calculate endpoint is deprecated in favour of the /tax_rates/calculate endpoint.
- Adds a
Deprecation: true
header (as per draft-ietf-httpapi-deprecation-header-01) to responses using deprecated pagination, with aLink
header linking to the updated documentation. After the deprecation dates, those endpoints will return a410 Gone
.
20170914
- Added the accounts API
- Added the
related_document
to the credit object. - Added
tax_1_transaction_type
andtax_2_transaction_type
fields to the taxes attributes in the credit object. - Added
transaction_type
fields to the document items attributes in the credit object. - Added
kind
,stripe_plan_id
,paypal_interval_unit
,paypal_interval_frequency
andpaypal_interval_duration
fields in the item object. - Changed
number
field limit to 20 characters forinvoices
,credits
andestimates
.
20170628
- Return 429 (
too many requests
) instead of 503 (service unavailable
) on rate limits.
20170418
- Added
custom_metadata
as an editable field.
20161108
country
is now returned as an attribute forinvoices
,expenses
,credits
andestimates
.
20160614
- Amounts are now returned as cents instead being formatted as amount with currency symbol.
- Versions are no longer passed as part of the URL, instead they can be passed as an accept header.
20160602
- First version.
- Amounts are returned formatted as amount with currency symbol.
Dev tools
We provide a testing environment, and a few endpoints and libraries to help with the development experience with the Quaderno set of APIs and products.
Testing environment
We provide a separate environment for testing purposes, with its own URL and credentials. We call it Quaderno Sandbox. Follow the link to learn about its special facilities to improve the development experience!
Purging Quaderno Sandbox
curl --user sk_your-secret-key:x \
--request POST \
--url http://{{your-account-name}}.sandbox-quadernoapp.com/api/purge
Our testing environment, Quaderno Sandbox, has a global 200 documents cap. You can manually purge all data with:
POST /purge
Pinging the API
curl --user sk_your-secret-key:x \
--url https://quadernoapp.com/api/ping
You can ping
Quaderno to check if the service is up, your credentials are correct, or to know your remaining API requests without doing an accountable request:
GET /ping
Libraries
To install our Ruby library, add the following to your Gemfile:
gem 'quaderno', require: 'quaderno-ruby'
You can use our API wrappers to connect and handle our APIs in a nicer way. We provide a Ruby library and a PHP library.
You may eventually find also a Swift library in our Github repos, but it doesn't currently support the latest API additions.
Ruby Wrapper
To configure our Ruby gem, just add this to your initializers:
Quaderno::Base.configure do |config|
config.auth_token = 'my_authenticate_token'
config.url = 'https://my_subdomain.quadernoapp.com/api/'
config.api_version = API_VERSION # Optional, defaults to the API version set in your account
end
Our Ruby library code is in https://github.com/quaderno/quaderno-ruby.
Please follow the README instructions to learn more.
PHP Wrapper
To setup the library:
require_once 'quaderno_load.php';
QuadernoBase::init('YOUR_API_KEY', 'YOUR_API_URL', API_VERSION);
QuadernoBase::ping(); // optionally test the API connection
Our PHP library code is in https://github.com/quaderno/quaderno-php and you may install it via Composer.
⚠️ There's a known limitation on the current version of the PHP wrapper which does not allow to easily inspect response headers, like the ones used to facilitate pagination (X-Pages-NextPage
and X-Pages-HasMore
). For now, you can workaround that either by playing with more requests with smaller data ranges and higher "limit" to fetch more docs (max 100); or fetch your first find data with any query and then perform another paginated request with the created_before
parameter and the ID of the last returned document, until the response is empty.
Simple example to request all pages from the given dates until all invoices have been returned:
$invoices = [];
$filters['limit'] = '50'; // bunches of 50 invoices per request
$filters['date']= "$from,$to"; // please use dates to avoid always getting all invoices
do {
$data = QuadernoInvoice::find($filters);
if (is_array($data) && !empty($data)) {
$invoices = array_merge($invoices, $data);
$last_invoice = end($data); // advances array's internal pointer to the last element, and returns its value
$filters['created_before'] = $last_invoice->id; // paginate from there
}
} while (!empty($data));
Please follow the README instructions to learn more.
CORE RESOURCES
Core resources are those shared across several API endpoints, like contacts
, products
or events
.
Contacts
A contact is any customer or vendor who appears on your invoices, credit notes, and expenses.
Create a contact
Contact creation example:
curl --request POST \
--url https://ACCOUNT_NAME.quadernoapp.com/api/contacts/ \
--user YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"kind": "person",
"first_name": "Susan",
"last_name": "Sanders",
"country": "United Kingdom",
"email": "anemail@example.com"
}'
The above command returns JSON structured like this:
{
"id": 1249481,
"city": null,
"country": "GB",
"created_at": 1632158561,
"email": "anemail@example.com",
"first_name": "Susan",
"full_name": "Susan Sanders",
"kind": "person",
"language": "EN",
"last_name": "Sanders",
"notes": null,
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
"phone_1": null,
"postal_code": null,
"processor": null,
"processor_id": null,
"region": null,
"street_line_1": null,
"street_line_2": null,
"tax_id": null,
"web": null
}
HTTP Request
POST /contacts
Parameters
Parameter | Type | Description |
---|---|---|
kind |
string | The type of contact. Values are company or person . Defaults to company . |
first_name |
string | The contact's first name / business name. Required |
last_name |
string | The contact's last name. Empty when the contact is a company . |
tax_id |
string | Any tax identification number. Quaderno can validate tax IDs from the EU, United Kingdom, Switzerland, Québec (Canada), Australia, and New Zealand. |
contact_person |
string | If the contact is a company , this is its contact person. |
street_line_1 |
string | Address line 1 (Street address/PO Box). |
street_line_2 |
string | Address line 2 (Apartment/Suite/Unit/Building). |
city |
string | City/District/Suburb/Town/Village. |
postal_code |
string | ZIP or postal code. Always predominates over region. |
region |
string | Region, province or state. When there are discrepancies with postal_code , region will be ignored and postal_code will be used to calculate the correct region . |
country |
string | 2-letter country code. |
phone_1 |
string | The contact's phone number. |
email |
string | The contact's email address. Multiple emails should be separated by commas. Maximum number of addresses allowed is 3. |
web |
string | The contact's website. |
language |
string | The contact's preferred language. Should be included in the account's translations list |
notes |
string | Internal notes about the contact. |
Retrieve a contact
GET /contacts/CONTACT_ID
curl https://ACCOUNT_NAME.quadernoapp.com/api/contacts/CONTACT_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"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":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
}
HTTP Request
GET /contacts/CONTACT_ID
Parameters
Parameter | Type | Description |
---|---|---|
CONTACT_ID |
integer | The ID of the contact to retrieve. Required |
Retrieve a contact by payment processor
curl https://ACCOUNT_NAME.quadernoapp.com/api/PAYMENT_PROCESSOR/customers/CUSTOMER_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"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":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
}
HTTP Request
GET /PAYMENT_PROCESSOR/customers/CUSTOMER_ID
Parameters
Parameter | Type | Description |
---|---|---|
PAYMENT_PROCESSOR |
string | The name of the payment processor. Can be stripe , paypal , gocardless and braintree . Required |
CUSTOMER_ID |
string | The ID of the contact in the payment processor. Required |
Update a contact
curl https://ACCOUNT_NAME.quadernoapp.com/api/contacts/CONTACT_ID \
-u YOUR_API_KEY:x \
-X PUT \
-H 'Content-Type: application/json' \
-d '{ "first_name": "Anthony" }'
The above command returns JSON structured like this:
{
"id":456987213,
"kind":"person",
"first_name":"Anthony",
"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":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
}
HTTP Request
PUT /contacts/CONTACT_ID
Parameters
Parameter | Type | Description |
---|---|---|
CONTACT_ID |
integer | The ID of the contact to update. Required |
kind |
string | The type of contact. Values are company or person . Defaults to company . |
first_name |
string | The contact's first name. Required |
last_name |
string | The contact's last name. Empty when the contact is a company . |
tax_id |
string | Any tax identification number. Quaderno can validate tax IDs from the EU, United Kingdom, Switzerland, Québec (Canada), Australia, and New Zealand. |
contact_person |
string | If the contact is a company , this is its contact person. |
street_line_1 |
string | Address line 1 (Street address/PO Box). |
street_line_2 |
string | Address line 2 (Apartment/Suite/Unit/Building). |
city |
string | City/District/Suburb/Town/Village. |
postal_code |
string | ZIP or postal code. |
region |
string | Region, province or state. |
country |
string | 2-letter country code. |
phone_1 |
string | The contact's phone number. |
email |
string | The contact's email address. Multiple emails should be separated by commas. Maximum number of addresses allowed is 3. |
web |
string | The contact's website. Validates format |
language |
string | The contact's preferred language. Should be included in the translations list |
notes |
string | Internal notes about the contact. |
Delete a contact
curl https://ACCOUNT_NAME.quadernoapp.com/api/contacts/CONTACT_ID \
-u YOUR_API_KEY:x \
-X DELETE \
-H 'Content-Type: application/json' \
HTTP Request
DELETE /contacts/CONTACT_ID
Parameters
Parameter | Type | Description |
---|---|---|
CONTACT_ID |
integer | The ID of the contact to delete. Required |
List all contacts
curl https://ACCOUNT_NAME.quadernoapp.com/api/contacts \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"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":"",
"email":"s.cooperphd@yahoo.com",
"web":"",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk",
},
{
"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":"",
"email":"info@apple.com",
"web":"http://apple.com",
"discount":null,
"tax_id":"",
"language":"EN",
"notes":"",
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/billing/4n0th3rp3rm4l1nk",
}
]
HTTP Request
GET /contacts
Parameters
Parameter | Type | Description |
---|---|---|
q |
string | A case-sensitive filter on the list based on the contact's full name, email or tax ID. |
Products
Products are all those goods or services that you sell to your customers, or that you buy to your providers to help run your business.
Products are internally equivalent to Document items from the Billing API, just with a slightly different set of fields. The advantage of this Products API is that you can manage the objects independently of the document they're being used on (invoices, expenses...). Both objects can be back-referenced via the product_code
field.
Create a product
curl https://ACCOUNT_NAME.quadernoapp.com/api/items \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"code": "prod_ebook1",
"name": "How to live with taxes",
"description": "Ebook for dealing with taxes",
"product_type": "service",
"unit_cost": "9.1",
"tax_class": "ebook",
"tax_type": "included"
}'
The above command returns JSON structured like this:
{
"id": 40057,
"code": "prod_ebook1",
"description": "Ebook for dealing with taxes",
"name": "How to live with taxes",
"product_type": "service",
"unit_cost": "9.1",
"stock": null,
"tax_class": "ebook",
"kind": "one_off",
"tax_type": "included",
"tax_based_on": "customer_country",
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/items/40057"
}
HTTP Request
POST /items
Parameters
Parameter | Type | Description |
---|---|---|
code |
string | The product’s SKU (Stock Keeping Unit) describe specific product variations, taking into account any combination of: attributes, currency, and cost. Required |
name |
string | The product’s name, meant to be displayable to the customer. Required |
unit_cost |
string | The unit amount in paise to be charged |
description |
string | The product’s description, meant to be displayable to the customer. Use this field to optionally store a long form explanation of the product being sold for your own rendering purposes. |
curency |
string | Three-letter ISO currency code, in uppercase. Must be a supported currency in your payment processors. |
tax_class |
string | The tax class that applies to the product. |
tax_type |
string | Specify if taxes are included or excluded in the unit_cost . Can be included , excluded . Defaults to excluded . |
product_type |
string | Specifies the product type. Can be either good or service . Defaults to service . |
kind |
string | The type of transaction. Can be one_off or subscription . Defaults to one_off |
stripe_id |
string | Only for Stripe subscriptions . ID of the Stripe price related to the subscription. |
paypal_interval_unit |
string | Only for PayPal subscriptions . Specifies billing frequency. Can be daily , weekly , monthly , yearly . |
paypal_interval_frequency |
integer | Only for PayPal subscriptions . The number of intervals between subscription billings. |
paypal_interval_duration |
integer | Only for subscriptions . Number of times the charge should recur. |
Retrieve a product
curl https://ACCOUNT_NAME.quadernoapp.com/api/items/PRODUCT_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 40057,
"code": "prod_ebook1",
"description": "Ebook for dealing with taxes",
"name": "How to live with taxes",
"product_type": "service",
"unit_cost": "9.1",
"stock": null,
"tax_class": "ebook",
"kind": "one_off",
"tax_type": "included",
"tax_based_on": "customer_country",
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/items/40057"
}
HTTP Request
GET /items/PRODUCT_ID
Parameters
Parameter | Type | Description |
---|---|---|
PRODUCT_ID |
integer | The ID of the product to retrieve. Required |
Update a product
curl https://ACCOUNT_NAME.quadernoapp.com/api/items/PRODUCT_ID \
-u YOUR_API_KEY:x \
-X PUT \
--header 'Content-Type: application/json' \
--data '{
"kind": "subscription",
"paypal_interval_duration": 6
}'
The above command converts the Product to a PayPal subscription, returning JSON structured like this:
{
"id": 40057,
"code": "prod_ebook1",
"description": "Ebook for dealing with taxes",
"name": "How to live with taxes",
"product_type": "service",
"unit_cost": "9.1",
"stock": null,
"tax_class": "ebook",
"kind": "subscription",
"tax_type": "included",
"tax_based_on": "customer_country",
"stripe_plan_id": "",
"paypal_interval_unit": "monthly",
"paypal_interval_frequency": null,
"paypal_interval_duration": 6,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/items/40057"
}
HTTP Request
PUT /items/PRODUCT_ID
Parameters
Parameter | Type | Description |
---|---|---|
PRODUCT_ID |
integer | The ID of the product to update. Required |
code |
string | The product’s SKU (Stock Keeping Unit) describe specific product variations, taking into account any combination of: attributes, currency, and cost. Required |
name |
string | The product’s name, meant to be displayable to the customer. Required |
unit_cost |
string | The unit amount in paise to be charged |
description |
string | The product’s description, meant to be displayable to the customer. Use this field to optionally store a long form explanation of the product being sold for your own rendering purposes. |
currency |
string | Three-letter ISO currency code, in uppercase. Must be a supported currency in your payment processors. |
tax_class |
string | The tax class that applies to the product. |
tax_type |
string | Specify if taxes are included or excluded in the unit_cost . Can be included , excluded . Defaults to excluded . |
product_type |
string | Specifies the product type. Can be either good or service . Defaults to service . |
kind |
string | The type of transaction. Can be one_off or subscription . Defaults to one_off |
stripe_id |
string | Only for Stripe subscriptions . ID of the Stripe price related to the subscription. |
paypal_interval_unit |
string | Only for PayPal subscriptions . Specifies billing frequency. Can be daily , weekly , monthly , yearly . |
paypal_interval_frequency |
integer | Only for PayPal subscriptions . The number of intervals between subscription billings. |
paypal_interval_duration |
integer | Only for subscriptions . Number of times the charge should recur. |
Delete a product
curl https://ACCOUNT_NAME.quadernoapp.com/api/items/PRODUCT_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /items/PRODUCT_ID
Parameters
Parameter | Type | Description |
---|---|---|
PRODUCT_ID |
integer | The ID of the product to delete. Required |
List all products
GET /items
curl https://ACCOUNT_NAME.quadernoapp.com/api/items \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 40057,
"code": "prod_ebook1",
"description": "Ebook for dealing with taxes",
"name": "How to live with taxes",
"product_type": "service",
"unit_cost": "9.1",
"stock": null,
"tax_class": "ebook",
"kind": "one_off",
"tax_type": "included",
"tax_based_on": "customer_country",
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/items/40057"
},
{
"id": 186698,
"code": "PRODUCT_SKU",
"description": null,
"name": "Titanic II: The revenge",
"product_type": "service",
"unit_cost": "99.0",
"tax_class": "eservice",
"kind": "one_off",
"tax_type": "excluded",
"tax_based_on": "customer_country",
}
]
HTTP Request
GET /items
Parameters
Parameter | Type | Description |
---|---|---|
q |
string | A case-sensitive filter on the list based on the product's code or name. |
Events
Events are our way of letting you know when something interesting happens in your account. When an interesting event occurs, we create a new Event
object and potentially send it to you in case you have registered its webhook.
For example, when a new invoice is created, we create a invoice.created
event; and when an invoice is deleted, we create an invoice.deleted
event.
The event object
{
"event_type":"invoice.created",
"account_id": 99999,
"data":
{
"object":
{
"id":925,
"contact_id":128,
"tag_list":[],
"number":"123346",
"issue_date":"2016-02-12",
"contact_name":"OrsonFarm",
"contact": {
"first_name":"OrsonFarm",
"last_name":null,
"email":"orso@farm.com",
"contact_person":"Orson Welles"
},
"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",
"account_id": 99999,
"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,
"email":null,
"web":null,
"discount":null,
"language":"EN",
"tax_id":null,
"currency":"USD",
"notes":null,
"processor_id":null,
"processor":null
}
}
}
{
"event_type":"payment.created",
"account_id": 99999,
"data":
{
"object":
{
"id":15,
"document_id":815,
"date":"2015-09-22",
"payment_method":"credit_card",
"amount_cents":400,
"amount":"4.00"
}
}
}
{
"event_type":"checkout.succeeded",
"account_id": 99999,
"data":{
"object":{
"transaction_details":{
"session":43,
"session_permalink":"https://demo.quadernoapp.com/checkout/session/8ccf3fdc42b85800188b113b81d3e4212ef094b3",
"gateway":"stripe",
"type":"charge",
"description":"Unicorn",
"customer":"cus_FXesSyaK3CG8Oz",
"email":"john@doe.com",
"transaction":"pi_1F2ZYtEjVHvINKlcq2as8H5V",
"product_id":"prod_61ffa845b4a0b8",
"tax_name":"VAT",
"tax_rate":20.0,
"extra_tax_name":null,
"extra_tax_rate":null,
"iat":1564647784,
"amount_cents":1500,
"amount":"15.00",
"currency":"EUR"
},
"contact":{
"id":547540,
"kind":"company",
"first_name":"John ",
"last_name":"Doe",
"full_name":"John Doe",
"contact_name":null,
"street_line_1":"Fake Street 1",
"postal_code":"SW15 5PU",
"city":null,
"region":null,
"country":"GB",
"email":"john@doe.com",
"web":null,
"language":"EN",
"tax_id":null,
}
}
}
}
{
"event_type":"checkout.failed",
"account_id": 99999,
"data":{
"object":{
"message":{
"response_message":"Insufficient funds",
"status_code":422
},
"transaction_details":{
"gateway":"stripe",
"type":"charge",
"description":"Unicorn",
},
"contact":{
"id":547540,
"kind":"company",
"first_name":"John ",
"last_name":"Doe",
"full_name":"John Doe",
"contact_name":null,
"street_line_1":"Fake Street 1",
"postal_code":"SW15 5PU",
"city":null,
"region":null,
"country":"GB",
"email":"john@doe.com",
"web":null,
"language":"EN",
"tax_id":null,
}
}
}
}
{
"event_type":"checkout.abandoned",
"account_id": 99999,
"data":{
"object":{
"transaction_details":{
"description":"Unicorn",
"plan":"awesome"
},
"contact":{
"first_name":"John ",
"last_name":"Doe",
"city":null,
"country":"GB",
"email":"john@doe.com"
}
}
}
}
All event uses the same data format, regardless of event type:
Parameter | Type | Description |
---|---|---|
event_type |
string | The event which triggered the webhook (invoice.created , contact.deleted , payment.created , etc). |
account_id |
integer | ID of the account that originated the event. |
data |
hash | A simplified JSON representation of the object. |
Types of events
This is a list of all the types of events we currently send via webhooks. We may add more at any time, so in developing and maintaining your code, you should not assume that only these types may exist.
Event types are a combination of the object you want to be notified about and the object state, like in resource.event
. Our goal is to design a consistent system that makes things easier to anticipate and code against.
Available events are:
account.updated
– Occurs whenever an account status or property has changed.account.application.deauthorized
– Occurs whenever a user deauthorizes an application. Sent to the related application only.checkout.succeeded
– Occurs when a checkout session has been successfully completed.checkout.failed
– Occurs when a checkout session fails.checkout.abandoned
– Occurs when a checkout session is abandoned by the customer.contact.created
– Occurs whenever a new contact is created.contact.updated
– Occurs whenever any property of a contact changes.contact.deleted
– Occurs whenever a contact is deleted.credit.created
– Occurs whenever a new credit note is created.credit.updated
– Occurs whenever a credit note changes (e.g., the credit amount).expense.created
– Occurs whenever a new expense is created.expense.updated
– Occurs whenever an expense changes (e.g., the expense amount).expense.deleted
– Occurs whenever an expense is deleted.invoice.created
– Occurs whenever a new invoice is created. Take a look at this event payload on the webhook payload examples section.invoice.updated
– Occurs whenever an invoice changes (e.g., the invoice amount).payment.created
– Occurs whenever a new payment is created.payment.deleted
– Occurs whenever a payment is deleted.reporting.request.suceeded
– Occurs whenever a requested report completed succesfully.reporting.request.failed
– Occurs whenever a requested report failed to complete.threshold.warning
- Occurs whenever a tax threshold is reached. Useful for contacting the tax nexus where you'll need to start filing taxes soon (or your tax advisor).threshold.exceeded
- Occurs whenever a tax threshold is reached. Useful for registering on the corresponding jurisdiction.threshold.eu.100k
- Occurs whenever sales of digital services within the EU (exclusive of VAT) reach €100,000. Applies only to EU-based sellers.
Endpoint errors
If your event-receiving endpoint does not respond with a 200
when Quaderno POST
s the data, Quaderno will try again within 48 hours.
If you fail to respond a second time then your subscription to that event will be deleted.
Checking the webhook signatures
Quaderno signs all webhook events it sends to your endpoints by including a signature in each event’s X-Quaderno-Signature
header. This allows you to verify that requests were sent by Quaderno and not by a third-party impersonating us.
The key is returned when you create a webhook or when you list all your webhooks.
To verify a request, you must generate a signature using the same secret key used by Quaderno and comparing the result to the value of the X-Quaderno-Signature
header.
In your code that processes webhooks:
- Create a string with the webhook's URL, exactly as you entered it in Quaderno (including any query string, if applicable). Quaderno always signs webhook requests with the exact URL you provided when you configured the webhook. A difference as small as including or removing a trailing slash will prevent the signature from validating.
- Append the
POST
body to the URL string, with no delimiter. The body should be a JSON-encoded representation of the data. - Hash the resulting string with
HMAC-SHA1
, using your webhook's authentication key to generate a binary signature. - Base64 encode the binary signature.
- Compare the binary signature that you generated to the signature provided in the
X-Quaderno-Signature
HTTP header.
TAXES
Tax IDs
Quaderno calculates taxes based on your tax settings. You need to set all your tax IDs from the jurisdictions where you're registered for tax collection in order to calculate taxes for that jurisdictions.
This API will allow you to manage your tax IDs and validate any tax IDs.
Create a tax ID
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_ids \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"jurisdiction_id": "6",
"value": "IE6388047V"
}'
The above command returns JSON structured like this:
{
"id": 235464,
"jurisdiction": {
"id": 6,
"country": "IE",
"name": "Ireland"
},
"state": "verified",
"valid_from": "2021-09-01",
"valid_until": null,
"value": "IE6388047V",
"created_at": 1632985500
}
HTTP Request
POST /tax_ids
Parameters
Parameter | Type | Description |
---|---|---|
jurisdiction_id |
integer | The tax jurisdiction. Required |
value |
decimal | The tax ID's value for the selected. Required |
valid_from |
date | The tax ID's validity date. Defaults to the beginning of the year. |
Retrieve a tax ID
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_ids/TAX_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 235464,
"jurisdiction": {
"id": 6,
"country": "IE",
"name": "Ireland"
},
"state": "verified",
"valid_from": "2021-09-01",
"valid_until": null,
"value": "IE6388047V",
"created_at": 1632985500
}
HTTP Request
GET /tax_ids/TAX_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_ID |
integer | The ID of the tax ID to retrieve. Required |
Update a tax ID
Updating your tax ID is allowed to fix mistakes when setting it. Updating your jurisdiction is not allowed.
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_ids/TAX_ID
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
-X PUT \
-d '{
"value": "21416127"
}'
The above command returns JSON structured like this:
{
"id": 235464,
"jurisdiction": {
"id": 6,
"country": "IE",
"name": "Ireland"
},
"state": "verified",
"valid_from": "2021-09-01",
"valid_until": null,
"value": "IE21416127",
"created_at": 1632985500
}
HTTP Request
PUT /tax_ids/TAX_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_ID |
integer | The ID of the tax ID to update. Required |
value |
decimal | The tax ID's value for the selected. Required |
valid_from |
date | The tax ID's validity date. |
valid_until |
date | The tax ID's expiration date. Use null if the tax ID has no expiration date. |
Delete a tax ID
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_ids/TAX_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /tax_ids/TAX_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_ID |
integer | The ID of the tax ID to delete. Required |
List all tax IDs
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_ids \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 456987213,
"jurisdiction": {
"id": 6,
"name": "Ireland"
},
"state": "verified",
"valid_from": "2015-01-01",
"valid_until": null,
"value": "IE6388047V",
"created_at": 1611322639
},
{
"id": 456982365,
"jurisdiction": {
"id": 73,
"name": "Luxembourg"
},
"state": "verified",
"valid_from": "2020-01-01",
"valid_until": null,
"value": "LU21416127",
"created_at": 1611322639
}
]
HTTP Request
GET /tax_ids
Validate a tax ID
Validating a tax ID is useful to learn if your customer is a business, so that you can apply reverse charge when selling to countries different that yours on B2B sales. Head to our Support Center to learn more about applying reverse charges.
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_ids/validate.json?country=IE&tax_id=IE6388047V \
-u YOUR_API_KEY:x \
The above command returns JSON structured like this:
{
"valid":true // this means your customer is a business
}
HTTP Request
GET /tax_ids/validate
Parameters
Parameter | Mandatory | Description |
---|---|---|
country |
Yes | The customer's country (2-letter ISO code) |
tax_id |
Yes | The customer's tax ID. |
The result can be true
(valid tax ID), false
(invalid tax ID) or null
(the external service is temporarily unavailable).
Please keep in mind that tax IDs are not actually validated while using the Sandbox. For testing purposes you can use the following IDs:
- DE111111111: invalid number, returns
false
- IE222222222: node down, returns
null
Custom Tax Rates
This API will allow you to customize your available tax rates for tax calculation.
Take into account that taxes are calculated based on the tax IDs you registered on the different Tax Jurisdictions. You may want to override these tax rates for greater control over the taxes you charge, by creating a Custom Tax Rate. We recommend using this option only as a last resort for special cases.
Create a custom tax rate
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"name": "XYZ",
"jurisdiction_id": 50,
"value": 10
}'
The above command returns JSON structured like this:
{
"id": 226655,
"type": "custom",
"jurisdiction": {
"id": 50,
"country": "US",
"name": "United States – Texas",
"region": "TX"
},
"name": "XYZ",
"tax_code": "eservice",
"valid_from": "2018-01-01",
"valid_until": null,
"value": 10.0,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/226655",
"created_at": 1628787498
}
HTTP Request
POST /tax_rates
Parameters
Parameter | Type | Description |
---|---|---|
jurisdiction_id |
integer | The tax jurisdiction. Required |
name |
string | The tax rate's name. Required |
value |
decimal | The tax rate's value for the selected tax_code . Required |
tax_code |
string | The tax code. Defaults to the account's default tax code. |
valid_from |
date | The tax rate's validity date. Defaults to the beginning of 3 years ago. |
Retrieve a tax rate
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/TAX_RATE_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 226655,
"type": "custom",
"jurisdiction": {
"id": 50,
"country": "US",
"name": "United States – Texas",
"region": "TX"
},
"name": "XYZ",
"tax_code": "eservice",
"valid_from": "2018-01-01",
"valid_until": null,
"value": 10.0,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/226655",
"created_at": 1628787498
}
HTTP Request
GET /tax_rates/TAX_RATE_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_RATE_ID |
integer | The ID of the tax rate to retrieve. Required |
Update a tax rate
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/TAX_RATE_ID \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
-X PUT \
--data '{
"jurisdiction_id": 50,
"value": 13
}'
The above command returns JSON structured like this:
{
"id": 226655,
"type": "custom",
"jurisdiction": {
"id": 50,
"country": "US",
"name": "United States – Texas",
"region": "TX"
},
"name": "XYZ",
"tax_code": "eservice",
"valid_from": "2018-01-01",
"valid_until": null,
"value": 13.0,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/226655",
"created_at": 1628787498
}
HTTP Request
PUT /tax_rates/TAX_RATE_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_RATE_ID |
integer | The ID of the tax rate to update. Required |
jurisdiction_id |
integer | The tax jurisdiction. Required |
name |
string | The tax rate's name. Required |
value |
decimal | The tax rate's value for the selected tax_code . Required |
tax_code |
string | The tax code. Defaults to the account's default tax code. |
valid_from |
date | The tax rate's validity date. Defaults to 3 years ago. |
valid_until |
date | The tax rate's expiration date. Use null if the tax rate has no expiration date. |
Delete a tax rate
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/TAX_RATE_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /tax_rates/TAX_RATE_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_RATE_ID |
integer | The ID of the tax rate to delete. Required |
List all tax rates
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 226655,
"type": "custom",
"jurisdiction": {
"id": 50,
"country": "US",
"name": "United States – Texas",
"region": "TX"
},
"name": "XYZ",
"tax_code": "eservice",
"valid_from": "2018-01-01",
"valid_until": null,
"value": 13.0,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/226655",
"created_at": 1628787498
},
{…},
{…}
]
HTTP Request
GET /tax_rates
List all available Tax Rates based on your registered Tax Jurisdictions.
Filtering by tax jurisdiction
Filtering is available using the query parameter jurisdiction_id
. Learn more about jurisdictions here.
Filtering by tax jurisdiction:
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/?jurisdiction_id=50 \
-u YOUR_API_KEY:x
Tax calculation
Tax calculations are based on your tax settings. You can calculate taxes only in tax jurisdictions where you're registered for tax collection. Please check your tax jurisdictions in your Quaderno account, or use the Tax IDs API to update your company's tax jurisdictions registration status.
Real-time tax calculation
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_rates/calculate?to_country=US&to_postal_code=90210&tax_code=standard&amount=10 \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"city": "BEVERLY HILLS",
"country": "US",
"county": "LOS ANGELES",
"currency": "EUR",
"name": "Sales tax",
"notes": null,
"product_type": "service", // informative field, either sent on request or default account's product type
"rate": 9.5,
"region": "CA",
"tax_behavior": "exclusive",
"tax_code": "standard",
"taxable_part": 100.0,
"taxable_base": 10.0,
"tax_amount": 0.95,
"total_amount": 10.95,
"status":"taxable"
}
Use this endpoint to calculate taxes during your checkout process. Tax calculations results are based on your tax settings and the customer's location.
We always recommend caching tax calculations, no matter if you have one shopping cart for multiple items or multiple carts with one item each. The recommended TTL is 24 hours.
HTTP Request
GET /tax_rates/calculate
Parameters
For jurisdictions that need to apply two tax rates (e.g. some Canadian provinces), the response will include the second tax rate on fields with the "
additional_
" suffix, like so:
{
"additional_name": "QST",
"additional_rate": 9.975,
"additional_taxable_part": 100.0,
"country": "CA",
"currency": "CAD",
"name": "GST",
"notes": null,
"product_type": "service",
"rate": 5.0,
"region": "QC",
"tax_behavior": "exclusive",
"tax_code": "standard",
"taxable_part": 100.0,
"import": false,
"subtotal": 100.0,
"tax_amount": 5.0,
"additional_tax_amount": 9.98,
"total_amount": 114.98,
"status":"taxable"
}
To understand the endpoint response, the formulae are:
taxable_base = amount * taxable_part
tax_amount = taxable_base * rate/100
total_amount = amount + tax_amount
Taxable base is the amount you must use to calculate the tax amount. It's usually 100% (taxable_part) of the amount of your product before taxes but there are a few exceptions. For example, in Texas the taxable base is 80% for SaaS products.
Parameter | Type | Description |
---|---|---|
to_country |
string | The customer's country. 2-letter ISO country code. Required |
to_postal_code |
string | The customer's postal code. Required whether the customer is based in the US or Canada. |
to_city |
string | The customer's city. It provides more accurate calculations in the US. |
tax_id |
string | The customer's tax ID. |
from_country |
string | The country where the order shipped from. 2-letter ISO country code. Defaults to the account's country. |
from_postal_code |
string | The postal code where the order shipped from. Defaults to the account's postal code. |
amount |
decimal | The transaction's amount. |
tax_code |
string | The transaction's tax code. Defaults to the account's default tax code. |
tax_behavior |
string | Specifies whether the price is considered inclusive of taxes or exclusive of taxes. Can be inclusive or exclusive . Defaults to exclusive . |
product_type |
string | Specifies whether the product is a good or a service. Can be good or service . Defaults to the account's default. |
date |
string | The transaction's date. Defaults to today. |
currency |
string | The transaction's currency. Three-letter ISO currency code, in uppercase. Defauts to the account's default currency. |
The status field response
⚠️ When calculating taxes in a jurisdiction you're not registered for tax collection, Quaderno will return an
not_registered
status
, along withnull
name
and a0.0
rate
:
{
"country": "CA",
"currency": "EUR",
"name": null, // prior to API version 20220325, use this to identify wrongly configured Tax Jurisdictions
"notes": null,
"product_type": "service",
"rate": 0.0,
"region": null,
"tax_behavior": "exclusive",
"tax_code": "eservice",
"taxable_part": null,
"import": true,
"subtotal": null,
"tax_amount": null,
"total_amount": null,
"status":"not_registered" // since API version 20220325
}
The status
field in the response contains a code indicating the reason of the calculation result (this field was added on API version 20220325):
taxable
: the transaction is taxable and the calculator will return a non-zero rate.non_taxable
: The transaction is non taxable if either the particular product is not taxable (ie. a digital product in California) or if the region for a particular territory within the registred jurisdiction is exempted (for example if you're registered in France selling to a customer from New Caledonia)not_registered
: You're trying to calculate taxes in a jurisdiction you're not registered in.reverse_charge
: The transaction is reverse charge so VAT won't be calculated
Tax Jurisdictions
A tax jurisdiction is an area subject to its own tax regulations. A tax jurisdiction can be a country, region, group of countries, province, state, city, county, district or any other area with a designated authority for tax purposes.
A businesses cannot collect taxes in a particular jurisdiction without being registered for tax collection with their tax authorities first.
You cannot create new tax jurisdictions, this API allows you to get all the tax jurisdictions so that you can then associate your tax id on those where you must collect taxes, using https://developers.quaderno.io/api/#create-a-tax-id. This action is equivalent to setting up a jurisdiction in the app via https://quadernoapp.com/settings/jurisdictions/new
Retrieve a jurisdiction
curl https://ACCOUNT_NAME.quadernoapp.com/api/jurisdictions/JURISDICTION_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 94,
"name": "Canada – British Columbia",
"country": "CA",
"region": "BC"
}
HTTP Request
GET /jurisdictions/JURISDICTION_ID
Parameters
Parameter | Type | Description |
---|---|---|
JURISDICTION_ID |
integer | The ID of the jurisdiction to retrieve. Required |
List all jurisdictions
GET /jurisdictions.json?country=CA
curl https://ACCOUNT_NAME.quadernoapp.com/api/jurisdictions.json?country=CA \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 94,
"name": "Canada – British Columbia",
"country": "CA",
"region": "BC"
},
{
"id": 95,
"name": "Canada – Quebec",
"country": "CA",
"region": "QC"
},
{
"id": 96,
"name": "Canada – Saskatchewan",
"country": "CA",
"region": "SK"
},
{
"id": 100,
"name": "Canada – Manitoba",
"country": "CA",
"region": "MB"
},
{
"id": 129,
"name": "Canada – GST/HST",
"country": "CA"
}
]
HTTP Request
GET /jurisdictions
Parameters
Parameter | Type | Description |
---|---|---|
country |
string | A case-sensitive filter on the list based on the jurisdiction's country. |
region |
string | A case-sensitive filter on the list based on the jurisdiction's region. Accepts the special string none to return jurisdictions that specifically have no region. |
Tax Codes
Tax codes classify goods and services for tax purposes.
Retrieve a tax code
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_codes/TAX_CODE_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": "eservice",
"description": "Service that is delivered over the internet. It is essentially automated, involves minimal human intervention and in the absence of information technology does not have viability.",
"name": "Electronically supplied services"
}
HTTP Request
GET /tax_codes/TAX_CODE_ID
Parameters
Parameter | Type | Description |
---|---|---|
TAX_CODE_ID |
integer | The ID of the tax code to retrieve. Required |
List all tax codes
curl https://ACCOUNT_NAME.quadernoapp.com/api/tax_codes \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": "consulting",
"description": "Any consulting and professional service (e.g., lawyers, designers, engineers, tax advisors, etc.).",
"name": "Consulting"
},
{
"id": "ebook",
"description": "An electronic book that is sold with unlimited use.",
"name": "eBook"
},
{
"id": "eservice",
"description": "Service that is delivered over the internet. It is essentially automated, involves minimal human intervention and in the absence of information technology does not have viability.",
"name": "Electronically supplied services"
},
{
"id": "exempt",
"description": "Any nontaxable good or service. No tax is applied for jurisdictions that impose a tax.",
"name": "Non-taxable"
},
{
"id": "reduced",
"description": "Specific goods and services with a reduced tax rate.",
"name": "Reduced tax rate"
},
{
"id": "saas",
"description": "Cloud services software delivered over the internet. The software is not customized for the specific buyer. Assumes no software is downloaded by the buyer.",
"name": "Software as a service (SaaS)"
},
{
"id": "standard",
"description": "Any taxable good or service. For jurisdictions that impose a tax, the standard rate is applied.",
"name": "Generally taxable"
}
]
HTTP Request
GET /tax_codes
Evidence
Location evidence are proofs of the customer's location that should be stored in order to be compliant with tax rules in some tax jurisdictions.
Create an evidence
POST /evidence
curl https://ACCOUNT_NAME.quadernoapp.com/api/evidence \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"document_id": 2009439,
"billing_country": "US",
"ip_address": "255.255.255.255",
"bank_country": "US"
}'
The above command returns JSON structured like this:
{
"id": 982855,
"document_id": 2009439,
"state": "confirmed",
"billing_country": "US",
"ip_address": "255.255.255.255",
"ip_country": null, // autocompleted when given a valid public IP
"bank_country": "US",
"additional_evidence": null,
"additional_evidence_country": null,
"notes": null
}
HTTP Request
POST /evidence
Parameters
Parameter | Type | Description |
---|---|---|
document_id |
integer | The ID of the related document. Required |
billing_country |
string | Customer's billing country (2-letter ISO code) |
ip_address |
string | Customer's IP address |
bank_country |
string | Customer's bank country (2-letter ISO code) |
additional_evidence |
string | An explanatory note about the additional evidence. Up to 255 chars. Required if additional_evidence_country is passed |
additional_evidence_country |
string | Additional evidence for the customer location (2-letter ISO code). Required if additional_evidence is passed) |
notes |
string | Additional notes about the evidence |
Retrieve an evidence
curl https://ACCOUNT_NAME.quadernoapp.com/api/evidence/EVIDENCE_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id":483,
"document_id":9748,
"state":"conflicting", // there are two non-matching evidence
"billing_country":"ES",
"ip_address":null,
"ip_country":null,
"bank_country":"US",
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
}
You may get
conflicting
alerts via thenotes
field, like in this example:
{
"id": 981519,
"document_id": 2008033,
"state": "conflicting",
"billing_country": "FR",
"ip_address": "255.255.255.255",
"ip_country": null,
"bank_country": "FR",
"additional_evidence": null,
"additional_evidence_country": null,
"notes": "Current tax (US) is different from customer location tax (FR)"
}
HTTP Request
GET /evidence/EVIDENCE_ID
Parameters
Parameter | Type | Description |
---|---|---|
EVIDENCE_ID |
integer | The ID of the evidence to retrieve. Required |
Update an evidence
curl https://ACCOUNT_NAME.quadernoapp.com/api/evidence/EVIDENCE_ID \
-u YOUR_API_KEY:x \
-X PUT \
--data '{
"billing_country": "FR",
"bank_country": "FR"
}'
The above command returns JSON structured like this:
{
"id":483,
"document_id":9748,
"state":"confirmed", // now two evidence match
"billing_country":"FR",
"ip_address": null,
"ip_country": null,
"bank_country":"FR",
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
}
HTTP Request
PUT /evidence/EVIDENCE_ID
Parameters
Parameter | Type | Description |
---|---|---|
EVIDENCE_ID |
integer | The ID of the evidence to update. Required |
document_id |
integer | The ID of the related document. Required |
billing_country |
string | Customer's billing country (2-letter ISO code) |
ip_address |
string | Customer's IP address |
bank_country |
string | Customer's bank country (2-letter ISO code) |
additional_evidence |
string | An explanatory note about the additional evidence. Up to 255 chars. Required if additional_evidence_country is passed |
additional_evidence_country |
string | Additional evidence for the customer location (2-letter ISO code). Required if additional_evidence is passed) |
notes |
string | Additional notes about the evidence |
List all evidence
curl https://ACCOUNT_NAME.quadernoapp.com/api/evidence \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id":487,
"document_id":9756,
"state":"unsettled", // example for missing evidence
"billing_country":"US",
"ip_address":null,
"ip_country":null,
"bank_country":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
},
{
"id":486,
"document_id":9755,
"state":"confirmed",
"billing_country":"ES",
"ip_address":"92.186.16.30",
"ip_country":"ES",
"bank_country":null,
"additional_evidence":null,
"additional_evidence_country":null,
"notes":null
},
]
HTTP Request
GET /evidence
Parameters
Parameter | Type | Description |
---|---|---|
state |
string | A case-sensitive filter on the list based on the evidence's state. You can combine multiple states separated by commas like ?state=confirmed,unsettled . Valid states are confirmed , unsettled and conflicting . |
document_id |
string | A case-sensitive filter on the list based on the document related to the evidence. |
BILLING
Invoices
An invoice is a detailed list of goods shipped or services rendered, with an account of all costs.
Create an invoice
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"contact": {
"first_name": "John"
},
"currency": "EUR",
"items_attributes": [
{
"product_code": "prod_ebook1"
},
{
"description": "A new product example",
"product_code": "prod_zzz",
"quantity": 1,
"total_amount": 100,
"discount_rate": 10,
"currency": "EUR",
"tax_1_name": "IVA",
"tax_1_rate": 21.0,
"tax_1_country": "",
"tax_1_region": null,
"tax_1_transaction_type": "standard"
}
],
"payment_method": "credit_card",
"custom_metadata": {
"a_custom_key": "a custom value"
},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
}'
The above command returns JSON structured like this:
{
"id": 2009439,
"blocked": false,
"number": "00022",
"issue_date": "2021-09-23",
"created_at": 1632395785,
"contact": {
"id": 1247386,
"full_name": "John"
},
"tax_id": null,
"country": "ES",
"street_line_1": null,
"street_line_2": null,
"city": null,
"region": null,
"postal_code": null,
"po_number": null,
"due_date": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5889679,
"product_code": "prod_ebook1",
"description": "How to live with taxes",
"reference": "prod_ebook1",
"quantity": "1.0",
"unit_price_cents": "910.0",
"discount_rate": "0.0",
"subtotal_cents": "910.0",
"discount_cents": "0.0",
"tax_1_name": null,
"tax_1_rate": null,
"tax_1_country": null,
"tax_1_region": null,
"tax_1_transaction_type": "eservice",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"gross_amount_cents": "910.0"
},
{
"id": 5889680,
"product_code": "prod_zzz",
"description": "A new product example",
"reference": "prod_zzz",
"quantity": "1.0",
"unit_price_cents": "9182.0",
"discount_rate": "10.0",
"subtotal_cents": "9182.2",
"discount_cents": "918.2",
"tax_1_name": "IVA",
"tax_1_rate": 21.0,
"tax_1_country": "ES",
"tax_1_region": null,
"tax_1_transaction_type": "standard",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"gross_amount_cents": "8264.0"
}
],
"subtotal_cents": "10092.0",
"discount_cents": "918.0",
"taxes": [
{
"label": "IVA (21%)",
"rate": 21.0,
"country": "ES",
"region": null,
"transaction_type": "standard",
"amount_cents": 1735
}
],
"total_cents": 10909,
"processor_fee_cents": null,
"payments": [
{
"id": 1411973,
"date": "2021-09-23",
"payment_method": "credit_card",
"amount_cents": 10909,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/invoices/2009439/payments/1411973.json"
}
],
"payment_details": null,
"notes": null,
"state": "outstanding",
"tag_list": [],
"secure_id": "33e50eb45e36965033cafda76a56ffa818ea5e86",
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/invoice/33e50eb45e36965033cafda76a56ffa818ea5e86?otp=152406",
"pdf": "https://ACCOUNT_NAME.quadernoapp.com/invoice/33e50eb45e36965033cafda76a56ffa818ea5e86.pdf?otp=152406",
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/invoices/2009439.json",
"processor": null,
"processor_id": null,
"custom_metadata": {
"a_custom_key": "a custom value"
},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
}
HTTP Request
POST /invoices
Parameters
Parameter | Type | Description |
---|---|---|
number |
string | A unique, sequential code that identifies the invoice. Legally, an invoice number sequence should never contain repeats or gaps. Automatic numbering is used by default. Manual numbering is discouraged. |
issue_date |
date | The date of the invoice's issue – not necessarily the date the products or services were provided. |
currency |
string | Three-letter ISO currency code, in uppercase. Defaults to the account's default currency. |
contact |
object | The data of the customer who will be billed. |
items_attributes |
array | Array of document items. Required |
payment_method |
string | Use this parameter to mark this document as paid in a single request. Can be credit_card , cash , wire_transfer , direct_debit , check , iou , paypal or other |
payment_processor |
string | The name of the payment processor used to take the payment. |
payment_processor_id |
string | The ID of the transaction in the payment_processor . |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
payment_details |
string | Detailed information about the accepted payment methods. |
notes |
string | Extra notes about the invoice. |
attachment |
object | The data of the file to be attached. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
verification_code |
string | The code needed to validate the invoice, usually generated by tax authorities. |
verification_permalink |
string | The link needed to validate the invoice, usually generated by tax authorities. |
Retrieve an invoice
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"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",
"custom_metadata":{},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
}
HTTP Request
GET /invoices/INVOICE_ID
Parameters
Parameter | Type | Description |
---|---|---|
INVOICE_ID |
integer | The ID of the invoice to retrieve. Required |
Update an invoice
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
-X PUT \
--data '{
"street_line_1": "New Address"
}'
...
The above command returns the whole object, now modified.
HTTP Request
PUT /invoices/INVOICE_ID
In our goal to make Quaderno compliant with tax rules worldwide, edition of invoices is limited to: po_number
, tag_list
, payment_details
, notes
, custom_metadata
and all those related to the billing address: street_line_1
,street_line_2
,city
,region
, and postal_code
.
If you need to make any changes to items, taxes, issue date or customer's tax ID, you'll need to issue a credit note and create a new invoice.
Parameters
Parameter | Type | Description |
---|---|---|
INVOICE_ID |
integer | The ID of the invoice to update. Required |
contact |
object | The data of the customer who will be billed. |
street_line_1 |
string | The customer's billing address line 1 (Street address/PO Box). |
street_line_2 |
string | The customer's billing address line 2 (Apartment/Suite/Unit/Building). |
city |
string | The customer's billing city. |
region |
string | The customer's billing state/province/region. |
postal_code |
string | The customer's billing ZIP or postal code. Available for updates. |
payment_processor |
string | The name of the payment processor used to take the payment. |
payment_processor_id |
string | The ID of the transaction in the payment_processor . |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
payment_details |
string | Detailed information about the accepted payment methods. |
attachment |
object | The data of the file to be attached. |
notes |
string | Extra notes about the invoice. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
verification_code |
string | The code needed to validate the invoice, usually generated by tax authorities. |
verification_permalink |
string | The link needed to validate the invoice, usually generated by tax authorities. |
Delete an invoice
In our goal to make Quaderno compliant with tax rules worldwide, it is not possible to delete invoices. If you want to cancel out an invoice, please issue a credit note.
Deliver an invoice
Use this endpoint to send an invoice to your customer.
Emails will be sent from notifications@quaderno.io
. If your customer replies to an invoice email, the reply will go to the email address you've set up on the business data page.
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/deliver \
-u YOUR_API_KEY:x
HTTP Request
GET /invoices/INVOICE_ID/deliver
Parameters
Parameter | Type | Description |
---|---|---|
INVOICE_ID |
integer | The ID of the invoice to deliver. Required |
List all invoices
curl -u YOUR_API_KEY:x \
-X GET 'https://ACCOUNT_NAME.quadernoapp.com/api/invoices'
The above command returns JSON structured like this:
[
{
"id":"507693322f412e0e2e00000f",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"contact":{
"id":"5073f9c22f412e02d0000032",
"full_name":"Garfield"
},
"country":"US",
"street_line_1":"Street 23",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"10203",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"lasagna",
"quantity":"25.0",
"unit_price_cents":"375",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Awesome!",
"subtotal_cents":"9375",
"discount_cents":"0",
"gross_amount_cents":"9375"
}
],
"subtotal_cents":"9375",
"discount_cents":"0",
"taxes":[],
"total_cents":"9375",
"payments":[
{
"id":"50aca7d92f412eda5200002c",
"date":"2012-11-21",
"payment_method":"credit_card",
"payment_processor":"stripe",
"payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
"amount_cents":"9375",
},
],
"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",
"custom_metadata":{},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
},
{
"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",
"custom_metadata":{},
"verification_code": "7XShGnyfgo",
"verification_permalink": "https://example.com/verification/7XShGnyfgo"
}
]
HTTP Request
GET /invoices
Parameters
Parameter | Type | Description |
---|---|---|
q |
string | A case-sensitive filter on the list based on the invoice's number or customer name. |
date |
string | A case-sensitive filter on the list based on the invoice's issue date. Pass the date range in the url like ?date=DATE1,DATE2 . Date range allows these formats: 2019-01-01,2019-12-31 or 2019/01/01,2019/12/31 . |
state |
string | A case-sensitive filter on the list based on the invoice's state. Can be outstanding , late , paid , refunded and archived . |
contact |
integer | A case-sensitive filter on the list based on the customer's ID. |
Credit Notes
A credit note is an official legal document used to notify your customer that credit is being applied. Usually a credit note is issued as a way to cancel out or correct an invoice by doing a full refund.
Credits always have an associated invoice which will be updated to state paid
when the credit is created.
Create a credit
curl https://ACCOUNT_NAME.quadernoapp.com/api/credits \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
-d '{
"invoice_id": 1
}'
The above command returns JSON structured like this:
{
"id": 3,
"blocked": false,
"number": "00001",
"issue_date": "2021-09-23",
"created_at": 1632386555,
"related_document": {
"id": 1,
"type": "Invoice"
},
"contact": {
"id": 1,
"full_name": "John Smith"
},
"tax_id": null,
"country": "ES",
"street_line_1": null,
"street_line_2": null,
"city": null,
"region": null,
"postal_code": null,
"po_number": null,
"due_date": null,
"currency": "USD",
"subject": null,
"exchange_rate": "0.852588",
"items": [
{
"id": 4,
"description": "Awesome ebook",
"quantity": "1.0",
"unit_price_cents": "1900.0",
"discount_rate": "0.0",
"tax_1_name": null,
"tax_1_rate": 0.0,
"tax_1_country": "US",
"tax_1_region": "NY",
"tax_1_transaction_type": "ebook",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": "US",
"tax_2_region": "NY",
"tax_2_transaction_type": "ebook",
"reference": null,
"subtotal_cents": "1900.0",
"discount_cents": "0.0",
"gross_amount_cents": "1900.0"
},
{
"id": 5,
"description": "Awesome SaaS",
"quantity": "1.0",
"unit_price_cents": "8175.0",
"discount_rate": "0.0",
"tax_1_name": "Sales tax",
"tax_1_rate": 8.875,
"tax_1_country": "US",
"tax_1_region": "NY",
"tax_1_transaction_type": "saas",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": "US",
"tax_2_region": "NY",
"tax_2_transaction_type": "saas",
"reference": null,
"subtotal_cents": "8175.0",
"discount_cents": "0.0",
"gross_amount_cents": "8175.0"
}
],
"subtotal_cents": "10075.0",
"discount_cents": "0.0",
"taxes": [
{
"label": "Sales tax (8.875%)",
"rate": 8.875,
"country": "US",
"region": "NY",
"transaction_type": "saas",
"amount_cents": 725
}
],
"total_cents": 10800,
"payments": [],
"payment_details": null,
"notes": null,
"state": "outstanding",
"tag_list": [
"tag-a",
"tag-b",
"tag-c"
],
"secure_id": "7hef1rs7p3rm4l1nk",
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/credit/b4a1539db4bca64f22a38c7f81e012a2555981dc",
"pdf": "https://ACCOUNT_NAME.quadernoapp.com/credit/b4a1539db4bca64f22a38c7f81e012a2555981dc.pdf",
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/credits/3.json",
"processor": null,
"processor_id": null,
"custom_metadata": {},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
}
HTTP Request
POST /credits
Parameters
Parameter | Type | Description |
---|---|---|
invoice_id |
integer | The ID of the refunded invoice. Required |
credited_amount |
decimal | The total amount to be credited. Only for refunds of invoices with one single item. The credited amount must be equal or lower than the total amount of the related invoice. |
payment_method |
string | Use this parameter to mark this document as paid in a single request. Can be credit_card , cash , wire_transfer , direct_debit , check , iou , paypal or other . |
Retrieve a credit
curl https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"related_document":{
"id":999999,
"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":"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/credit/7hef1rs7p3rm4l1nk",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/credit/7hef1rs7p3rm4l1nk.pdf",
"custom_metadata":{},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
}
HTTP Request
GET /credits/CREDIT_ID
Parameters
Parameter | Type | Description |
---|---|---|
CREDIT_ID |
integer | The ID of the credit to retrieve. Required |
Update a credit
curl https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID \
-u YOUR_API_KEY:x \
-X PUT \
-d '{ "notes": "You better pay this time, Tony",
"custom_metadata": {
"memo": "I think he is not paying again."
}
}'
The above command returns JSON structured like this:
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"related_document":{
"id":999999,
"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":"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/credit/7hef1rs7p3rm4l1nk",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/credit/7hef1rs7p3rm4l1nk.pdf",
"custom_metadata":{},
"verification_code": "08V9SBg49G",
"verification_permalink": "https://example.com/verification/08V9SBg49G"
}
HTTP Request
PUT /credits/CREDIT_ID
In our goal to make Quaderno compliant with tax rules worldwide, the edition of credit notes is limited to: po_number
, tag_list
, payment_details
, notes
, custom_metadata
and all those related to the billing address: street_line_1
,street_line_2
,city
,region
, and postal_code
.
Parameters
Parameter | Type | Description |
---|---|---|
CREDIT_ID |
integer | The ID of the credit to update. Required |
contact |
object | The data of the customer who was refunded. |
street_line_1 |
string | The customer's billing address line 1 (Street address/PO Box). |
street_line_2 |
string | The customer's billing address line 2 (Apartment/Suite/Unit/Building). |
city |
string | The customer's billing city. |
region |
string | The customer's billing state/province/region. |
postal_code |
string | The customer's billing ZIP or postal code. Available for updates. |
payment_processor |
string | The name of the payment processor used to refund the payment. |
payment_processor_id |
string | The ID of the transaction in the payment_processor . |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
payment_details |
string | Detailed information about the accepted payment methods. |
notes |
string | Extra notes about the credit. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
verification_code |
string | The code needed to validate the credit note, usually generated by tax authorities. |
verification_permalink |
string | The link needed to validate the credit note, usually generated by tax authorities. |
Void a credit
As invoices cannot be deleted, you can "convert" an invoice into a credit note with POST /credits
which takes the invoice id as parameter. In case you also need to correct this, mark the associated credit note as void using this endpoint.
curl https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID/void \
-u YOUR_API_KEY:x \
-X PUT
This returns the whole credit. Note that it will contain a
"related_document": { "id": 2005801, "type": "Invoice"},
and a"state": "void",
.
HTTP Request
PUT /credits/CREDIT_ID/void
Parameters
Parameter | Type | Description |
---|---|---|
CREDIT_ID |
integer | The ID of the credit to void. Required |
Deliver a credit
Use this endpoint to send a credit note to your customer.
Emails will be sent from notifications@quaderno.io
. If your customer replies to an invoice email, the reply will go to the email address you've set up on the business data page.
curl https://ACCOUNT_NAME.quadernoapp.com/api/credits/CREDIT_ID/deliver \
-u YOUR_API_KEY:x
HTTP Request
GET /credits/CREDIT_ID/deliver
Parameters
Parameter | Type | Description |
---|---|---|
CREDIT_ID |
integer | The ID of the credit to deliver. Required |
List all credits
curl https://ACCOUNT_NAME.quadernoapp.com/api/credits \
-u YOUR_API_KEY:x
[
{
"id":"507693322f412e0e2e00000f",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376000",
"related_document":{
"id":999999,
"type":"Invoice"
},
"contact":{
"id":"5073f9c22f412e02d0000032",
"full_name":"Garfield"
},
"country":"US",
"street_line_1":"Street 23",
"street_line_2":"",
"city":"New York",
"region":"New York",
"postal_code":"10203",
"po_number":null,
"due_date":null,
"currency":"USD",
"exchange_rate":"0.680309",
"items":[
{
"id":"48151623429",
"description":"lasagna",
"quantity":"25.0",
"unit_price_cents":"375",
"discount_rate":"0.0",
"tax_1_name":"",
"tax_1_rate":"",
"tax_2_name":"",
"tax_2_rate":"",
"reference":"Awesome!",
"subtotal_cents":"9375",
"discount_cents":"0",
"gross_amount_cents":"9375"
}
],
"subtotal_cents":"9375",
"discount_cents":"0",
"taxes":[],
"total_cents":"9375",
"payments":[
{
"id":"50aca7d92f412eda5200002c",
"date":"2012-11-21",
"payment_method":"credit_card",
"payment_processor":"stripe",
"payment_processor_id":"ch_19yUdh2eZvKYlo2CkFVBOZG7",
"amount_cents":"9375",
"url":"https://ACCOUNT_NAME.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",
"custom_metadata":{},
"verification_code": "7XShGnyfgo",
"verification_permalink": "https://example.com/verification/7XShGnyfgo"
},
{
"id":"507693322f412e0e2e0000da",
"number":"0000047",
"issue_date":"2012-10-11",
"created_at":"1325376020",
"related_document":{
"id":875978,
"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://ACCOUNT_NAME.quadernoapp.com/credit/7hes3c0ndp3rm4l1nk",
"pdf":"https://ACCOUNT_NAME.quadernoapp.com/credit/7hes3c0ndp3rm4l1nk.pdf",
"custom_metadata":{},
"verification_code": "7XShGnyfgo",
"verification_permalink": "https://example.com/verification/7XShGnyfgo"
}
]
HTTP Request
GET /credits
Parameters
Parameter | Type | Description |
---|---|---|
q |
string | A case-sensitive filter on the list based on the credit's number or customer name. |
date |
string | A case-sensitive filter on the list based on the credit's issue date. Pass the date range in the url like ?date=DATE1,DATE2 . Date range allows these formats: 2019-01-01,2019-12-31 or 2019/01/01,2019/12/31 . |
state |
string | A case-sensitive filter on the list based on the credit's state. Can be outstanding , late , paid , void and archived . |
Expenses
Expenses are all the expenses that you receive from your providers.
Create an expense
curl https://ACCOUNT_NAME.quadernoapp.com/api/expenses \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"contact":{
"id": 1249483
},
"items_attributes":[
{
"description":"Office supplies",
"quantity":"30",
"unit_price":"12.5",
"tax_1_rate": "20",
"tax_1_country": "GB",
"tax_1_name": "VAT",
"tax_1_transaction_type": "good"
},
{
"description":"Snacks",
"quantity":"10",
"total_amount":"1000",
"tax_1_rate": "20",
"tax_1_country": "GB",
"tax_1_name": "VAT",
"tax_1_transaction_type": "good"
}
],
"payment_method": "cash"
}'
The above command returns JSON structured like this:
{
"id": 2009894,
"blocked": false,
"issue_date": "2021-09-27",
"created_at": 1632729442,
"contact": { // your provider contact info
"id": 1249483,
"full_name": "Susan Sanders"
},
"tax_id": null,
"country": "GB",
"street_line_1": null,
"street_line_2": null,
"city": null,
"region": null,
"postal_code": null,
"po_number": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890331,
"product_code": null,
"description": "Office supplies",
"quantity": "30.0",
"unit_price_cents": "1250.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice", // safely to ignore as rate is null
"reference": null,
"subtotal_cents": "37500.0",
"discount_cents": "0.0",
"gross_amount_cents": "37500.0"
},
{
"id": 5890332,
"product_code": null,
"description": "Snacks",
"quantity": "10.0",
"unit_price_cents": "8333.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": null,
"subtotal_cents": "83333.0",
"discount_cents": "0.0",
"gross_amount_cents": "83333.0"
}
],
"subtotal_cents": "120833.0",
"discount_cents": "0.0",
"taxes": [ // summary of expense's calculated taxes
{
"label": "VAT (20%)",
"name": "VAT",
"rate": 20.0,
"country": "GB",
"region": null,
"transaction_type": "good",
"taxable_base_cents": "120833.0",
"amount_cents": 24167
}
],
"total_cents": 145000,
"payments": [
{
"id": 1412382,
"date": "2021-09-27",
"payment_method": "cash",
"amount_cents": 145000,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/expenses/2009894/payments/1412382.json"
}
],
"notes": null,
"state": "paid",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/expenses/2009894.json",
"custom_metadata": {}
}
HTTP Request
POST /expenses
Parameters
Parameter | Type | Description |
---|---|---|
issue_date |
date | The date of the expense's issue – not necessarily the date the products or services were provided. |
currency |
string | Three-letter ISO currency code, in uppercase. Defaults to the account's default currency. |
contact |
object | The data of the provider. |
items_attributes |
array | Array of document items. Required |
payment_method |
string | Use this parameter to mark this document as paid in a single request. Can be credit_card , cash , wire_transfer , direct_debit , check , iou , paypal or other |
payment_processor |
string | The name of the payment processor used to take the payment. |
payment_processor_id |
string | The ID of the transaction in the payment_processor . |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
notes |
string | Extra notes about the expense. |
attachment |
object | The data of the file to be attached. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Retrieve an expense
curl https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 2009894,
"blocked": false,
"issue_date": "2021-09-27",
"created_at": 1632729442,
"contact": {
"id": 1249483,
"full_name": "Susan Sanders"
},
"tax_id": null,
"country": "GB",
"street_line_1": null,
"street_line_2": null,
"city": null,
"region": null,
"postal_code": null,
"po_number": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890331,
"product_code": null,
"description": "Office supplies",
"quantity": "30.0",
"unit_price_cents": "1250.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": null,
"subtotal_cents": "37500.0",
"discount_cents": "0.0",
"gross_amount_cents": "37500.0"
},
{
"id": 5890332,
"product_code": null,
"description": "Snacks",
"quantity": "10.0",
"unit_price_cents": "8333.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": null,
"subtotal_cents": "83333.0",
"discount_cents": "0.0",
"gross_amount_cents": "83333.0"
}
],
"subtotal_cents": "120833.0",
"discount_cents": "0.0",
"taxes": [
{
"label": "VAT (20%)",
"name": "VAT",
"rate": 20.0,
"country": "GB",
"region": null,
"transaction_type": "good",
"taxable_base_cents": "120833.0",
"amount_cents": 24167
}
],
"total_cents": 145000,
"payments": [
{
"id": 1412382,
"date": "2021-09-27",
"payment_method": "cash",
"amount_cents": 145000,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/expenses/2009894/payments/1412382.json"
}
],
"notes": null,
"state": "paid",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/expenses/2009894.json",
"custom_metadata": {}
}
HTTP Request
GET /expenses/EXPENSE_ID
Parameters
Parameter | Type | Description |
---|---|---|
EXPENSE_ID |
integer | The ID of the expense to retrieve. Required |
Update an expense
curl https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID \
-u YOUR_API_KEY:x \
-X PUT \
--header 'Content-Type: application/json' \
--data '{
"items_attributes":[
{
"id":5890402,
"description": "2 Printer cartridges and consumable batteries."
}
]
}'
The above command modifies the inner
items
object (response trimmed for brevity):
…
"items": [
{
"id": 5890402,
"product_code": null,
"description": "2 Printer cartridges and consumable batteries.", // updated data
"quantity": "30.0",
"unit_price_cents": "1250.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": null,
"subtotal_cents": "37500.0",
"discount_cents": "0.0",
"gross_amount_cents": "37500.0"
},
…
Adding an attachment example (base64 trimmed for brevity):
curl https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID \
-u YOUR_API_KEY:x \
-X PUT \
--header 'Content-Type: application/json' \
--data '{
"attachment": {
"filename": "ticket.png",
"data": "iVBORw0KGgo … " // do not include any `data:image/png;base64,` suffix on `data`
}
}'
The
attachment
is not returned on the response. A200 OK
response means the file was successfully attached to the expense.
HTTP Request
PUT /expenses/EXPENSE_ID
Parameters
Parameter | Type | Description |
---|---|---|
EXPENSE_ID |
integer | The ID of the expense to update. Required |
issue_date |
date | The date of the expense's issue - not necessarily the date the products or services were provided. |
contact |
object | The data of the provider. |
items_attributes |
array | Array of expense items. Required |
payment_processor |
string | The name of the payment processor used to take the payment. |
payment_processor_id |
string | The ID of the transaction in the payment_processor . |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
notes |
string | Extra notes about the expense. |
attachment |
object | The data of the file to be attached. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Delete an expense
curl https://ACCOUNT_NAME.quadernoapp.com/api/expenses/EXPENSE_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /expenses/EXPENSE_ID
Parameters
Parameter | Type | Description |
---|---|---|
EXPENSE_ID |
integer | The ID of the expense to delete. Required |
List all expenses
curl https://ACCOUNT_NAME.quadernoapp.com/api/expenses \
-u YOUR_API_KEY:x
The above command returns a JSON list of expenses, which you can filter (see parameters):
[
{
"id": 2009927,
"blocked": false,
"issue_date": "2021-09-27",
"created_at": 1632735325,
"contact": {
"id": 1249483,
"full_name": "Susan Sanders"
},
"tax_id": null,
"country": "GB",
"street_line_1": null,
"street_line_2": null,
"city": null,
"region": null,
"postal_code": null,
"po_number": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890402,
"product_code": null,
"description": "2 Printer cartridges and consumable batteries.",
"quantity": "30.0",
"unit_price_cents": "1250.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": null,
"subtotal_cents": "37500.0",
"discount_cents": "0.0",
"gross_amount_cents": "37500.0"
},
{
"id": 5890403,
"product_code": null,
"description": "Snacks",
"quantity": "10.0",
"unit_price_cents": "8333.0",
"discount_rate": "0.0",
"tax_1_name": "VAT",
"tax_1_rate": 20.0,
"tax_1_country": "GB",
"tax_1_region": null,
"tax_1_transaction_type": "good",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": null,
"subtotal_cents": "83333.0",
"discount_cents": "0.0",
"gross_amount_cents": "83333.0"
}
],
"subtotal_cents": "120833.0",
"discount_cents": "0.0",
"taxes": [
{
"label": "VAT (20%)",
"name": "VAT",
"rate": 20.0,
"country": "GB",
"region": null,
"transaction_type": "good",
"taxable_base_cents": "120833.0",
"amount_cents": 24167
}
],
"total_cents": 145000,
"payments": [
{
"id": 1412412,
"date": "2021-09-27",
"payment_method": "cash",
"amount_cents": 145000,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/expenses/2009927/payments/1412412.json"
}
],
"notes": "Example note",
"state": "paid",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/expenses/2009927.json",
"custom_metadata": {}
},
{ … },
{ … }
]
HTTP Request
GET /expenses
Parameters
Parameter | Type | Description |
---|---|---|
q |
string | A case-sensitive filter on the list based on the provider's name or the PO number. |
date |
string | A case-sensitive filter on the list based on the expense's issue date. Pass the date range in the url like ?date=DATE1,DATE2 . Date range allows these formats: 2019-01-01,2019-12-31 or 2019/01/01,2019/12/31 . |
state |
string | A case-sensitive filter on the list based on the expense's state. Can be outstanding , late , and paid . |
contact |
integer | A case-sensitive filter on the list based on the provider's ID. |
Recurring Documents
A "recurring" is a special document that periodically renews itself, generating a recurring invoice or expense. You can use recurring documents to automatically send periodic invoices to your customers, or to track recurring business bills.
Create a recurring
curl https://ACCOUNT_NAME.quadernoapp.com/api/recurring \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"contact": {
"id": 1251096
},
"items_attributes": [ {
"product_code": "prod_consulting"
} ],
"payment_details": "Payment due upon receipt via wire transfer",
"notes": "This recurring uses existing contact and products, and relies on defaults so that an invoice will be sent monthly."
}'
The above command returns JSON structured like this:
{
"id": 2010192,
"recurring_document": "invoice",
"start_date": "2021-10-28",
"end_date": null,
"frequency": "monthly",
"recurring_period": "months",
"recurring_frequency": 1,
"contact": {
"id": 1251096,
"full_name": "Susan Sanders"
},
"po_number": null,
"due_days": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890845,
"product_code": "prod_consulting",
"description": "Consultancy services",
"quantity": "1.0",
"unit_price_cents": "50000.0",
"discount_rate": "0.0",
"tax_1_name": null,
"tax_1_rate": null,
"tax_1_country": null,
"tax_1_region": null,
"tax_1_transaction_type": "eservice",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": "prod_consulting",
"subtotal_cents": "50000.0",
"discount_cents": "0.0",
"gross_amount_cents": "50000.0"
}
],
"subtotal": "50000.0",
"discount": "0.0",
"taxes": [],
"total_cents": 50000,
"payment_details": "Payment due upon receipt via wire transfer",
"notes": "This recurring uses existing contact and products, and relies on defaults so that an invoice will be sent monthly.",
"state": "active",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/recurring/2010192.json",
"custom_metadata": {}
}
HTTP Request
POST /recurring
Parameters
Parameter | Type | Description |
---|---|---|
recurring_document |
string | Can be invoice or expense . Defaults to invoice |
start_date |
date | Date of the issue of the first document for this recurring. Defaults to 1 months from today. |
end_date |
date | Date of the issue of the last document for this recurring. |
frequency |
string | Can be daily , weekly , biweekly , monthly , bimonthly , quarterly , semiyearly , yearly , biyearly . Defaults to monthly . |
delivery |
string | Can be send , create , nothing . Defaults to send . |
currency |
string | Three-letter ISO currency code, in uppercase. Defaults to the account's default currency. |
contact |
object | The data of the customer or the provider. |
items_attributes |
array | Array of document items. Required |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
payment_details |
string | Detailed information about the accepted payment methods. |
notes |
string | Extra notes about the invoice. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Retrieve a recurring
curl https://ACCOUNT_NAME.quadernoapp.com/api/recurring/RECURRING_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 2010192,
"recurring_document": "invoice",
"start_date": "2021-10-28",
"end_date": null,
"frequency": "monthly",
"recurring_period": "months",
"recurring_frequency": 1,
"contact": {
"id": 1251096,
"full_name": "Susan Sanders"
},
"po_number": null,
"due_days": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890845,
"product_code": "prod_consulting",
"description": "Consultancy services",
"quantity": "1.0",
"unit_price_cents": "50000.0",
"discount_rate": "0.0",
"tax_1_name": null,
"tax_1_rate": null,
"tax_1_country": null,
"tax_1_region": null,
"tax_1_transaction_type": "eservice",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": "prod_consulting",
"subtotal_cents": "50000.0",
"discount_cents": "0.0",
"gross_amount_cents": "50000.0"
}
],
"subtotal": "50000.0",
"discount": "0.0",
"taxes": [],
"total_cents": 50000,
"payment_details": "Payment due upon receipt via wire transfer",
"notes": "This recurring uses existing contact and products, and relies on defaults so that an invoice will be sent monthly.",
"state": "active",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/recurring/2010192.json",
"custom_metadata": {}
}
HTTP Request
GET /recurring/RECURRING_ID
Parameters
Parameter | Type | Description |
---|---|---|
RECURRING_ID |
integer | The ID of the recurring to retrieve. Required |
Update a recurring
This example applies a discount for the next recurring documents.
curl https://ACCOUNT_NAME.quadernoapp.com/api/recurring/RECURRING_ID \
-u YOUR_API_KEY:x \
-X PUT \
--header 'Content-Type: application/json' \
--data '{
"items_attributes": [
{
"id": 5890845,
"discount_rate": 10
}
]
}'
The above command returns JSON structured like this:
{
"id": 2010192,
"recurring_document": "invoice",
"start_date": "2021-10-28",
"end_date": null,
"frequency": "monthly",
"recurring_period": "months",
"recurring_frequency": 1,
"contact": {
"id": 1251096,
"full_name": "Susan Sanders"
},
"po_number": null,
"due_days": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890845,
"product_code": "prod_consulting",
"description": "Consultancy services",
"quantity": "1.0",
"unit_price_cents": "50000.0",
"discount_rate": "10.0", // updated data
"tax_1_name": null,
"tax_1_rate": null,
"tax_1_country": null,
"tax_1_region": null,
"tax_1_transaction_type": "eservice",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": "prod_consulting",
"subtotal_cents": "50000.0",
"discount_cents": "5000.0",
"gross_amount_cents": "45000.0"
}
],
"subtotal": "100000.0",
"discount": "10000.0",
"taxes": [],
"total_cents": 90000,
"payment_details": "Payment due upon receipt via wire transfer",
"notes": "This recurring uses existing contact and products, and relies on defaults so that an invoice will be sent monthly.",
"state": "active",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/recurring/2010192.json",
"custom_metadata": {}
}
You can modify all fields and items of the recurring to apply these changes on the next recurring documents. If you need to remove items from the recurring, use the parameter _destroy
set to 1
as documented in document items.
HTTP Request
PUT /recurring/RECURRING_ID
Parameters
Parameter | Type | Description |
---|---|---|
RECURRING_ID |
integer | The ID of the recurring to retrieve. Required |
end_date |
date | Date of the issue of the last document for this recurring. |
delivery |
string | Can be send , create , nothing . Defaults to send . |
currency |
string | Three-letter ISO currency code, in uppercase. Defaults to the account's default currency. |
contact |
object | The data of the customer or the provider. |
items_attributes |
array | Array of document items. Required |
po_number |
string | The number of the related order. |
tag_list |
string | Multiple tags should be separated by commas |
payment_details |
string | Detailed information about the accepted payment methods. |
notes |
string | Extra notes about the invoice. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Delete a recurring
curl https://ACCOUNT_NAME.quadernoapp.com/api/recurring/RECURRING_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /recurring/RECURRING_ID
Parameters
Parameter | Type | Description |
---|---|---|
RECURRING_ID |
integer | The ID of the recurring to delete. Required |
List all recurring
curl https://ACCOUNT_NAME.quadernoapp.com/api/recurring \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 2010192,
"recurring_document": "invoice",
"start_date": "2021-10-28",
"end_date": null,
"frequency": "monthly",
"recurring_period": "months",
"recurring_frequency": 1,
"contact": {
"id": 1251096,
"full_name": "Susan Sanders"
},
"po_number": null,
"due_days": null,
"currency": "EUR",
"subject": null,
"items": [
{
"id": 5890845,
"product_code": "prod_consulting",
"description": "Consultancy services",
"quantity": "1.0",
"unit_price_cents": "50000.0",
"discount_rate": "10.0",
"tax_1_name": null,
"tax_1_rate": null,
"tax_1_country": null,
"tax_1_region": null,
"tax_1_transaction_type": "eservice",
"tax_2_name": null,
"tax_2_rate": null,
"tax_2_country": null,
"tax_2_region": null,
"tax_2_transaction_type": "eservice",
"reference": "prod_consulting",
"subtotal_cents": "50000.0",
"discount_cents": "5000.0",
"gross_amount_cents": "45000.0"
}
],
"subtotal": "100000.0",
"discount": "10000.0",
"taxes": [],
"total_cents": 90000,
"payment_details": "Payment due upon receipt via wire transfer",
"notes": "This recurring uses existing contact and products, and relies on defaults so that an invoice will be sent monthly.",
"state": "active",
"tag_list": [],
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/recurring/2010192.json",
"custom_metadata": {}
},
{…},
{…}
]
HTTP Request
GET /recurring
Parameters
Parameter | Type | Description |
---|---|---|
q |
string | A case-sensitive filter on the list based on the recurring's contact or PO number. |
date |
string | A case-sensitive filter on the list based on the invoice's issue date. Pass the date range in the url like ?date=DATE1,DATE2 . Date range allows these formats: 2019-01-01,2019-12-31 or 2019/01/01,2019/12/31 . |
state |
string | A case-sensitive filter on the list based on the invoice's state. Can be active and archived . |
contact |
integer | A case-sensitive filter on the list based on the contact's ID. |
Document Items
Invoices, credits, and expenses are made up of one or more document items, created via an array of items_attributes
on the Billing API.
Document items are internally equivalent to Products which use a slighty different API. However, both can be back-referenced via the product_code
field.
Each document item has the following properties:
Parameter | Type | Description |
---|---|---|
id |
integer | Unique identifier for the object. Available only for updates |
description |
string | An arbitrary string attached to the object. Often useful for displaying to users. Required |
product_code |
string | Code of an existing product. If present, you don't need to set the description . |
quantity |
decimal | Quantity of units for the document item. Defaults to 1. |
unit_price |
decimal | The unit price of the item before any discount or tax is applied. Required if total_amount is not passed. |
total_amount |
decimal | The total amount to be charged after discounts and taxes. Required if unit_price is not passed. |
discount_rate |
decimal | This represents the discount percent out of 100, if applicable. |
tax_1_name |
string | The name of the tax, if applicable. Required if tax_1_rate is present |
tax_1_rate |
decimal | The rate of the tax, if applicable. Required if tax_1_name is present |
tax_1_country |
string | Tax country, if applicable. Defaults to the contact's country |
tax_1_region |
string | Tax region, if applicable. Only for US sales tax and Canada GST. |
tax_1_transaction_type |
string | Type of transaction. Can be any tax_code . Defaults to the account's default tax class. |
tax_2_name |
string | The name of the additional tax, if applicable. Required if tax_2_rate is present |
tax_2_rate |
decimal | The rate of the additional tax, if applicable. Required if tax_2_name is present |
tax_2_country |
string | Tax country, if applicable. Defaults to the contact's country |
tax_2_region |
string | Tax region, if applicable. Only for US sales tax and Canada GST. |
tax_2_transaction_type |
string | Type of transaction. Can be any tax_code . Defaults to the account's default tax class. |
_destroy |
integer | Set it to 1 if you want to remove the document item selected by ID. Available only for updates. |
Attachments
Invoices and expenses can have attached files. Each attachment has the following properties:
Parameter | Type | Description |
---|---|---|
id |
integer | Unique identifier for the object. Available only for updates. |
data |
string | File data enconded in base64. Required. |
filename |
string | Name of the attached file. Required. |
public |
integer | Visibility of attached file. Public attachments can be seen and downloaded by customers. Defaults to false. |
Payments
Payments in Quaderno-lingo represent the recording of a successful payment.
Like products, payments are a sub-object and can be attached to invoices, expenses and credits. However, payments cannot exist in Quaderno separately to an instance of one of these classes, and the same payment cannot be referenced by more than one instance.
Create a payment
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/payments \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"amount": 9.99,
"payment_method": "credit_card" \
"payment_processor": "stripe" \
"payment_processor_id": "ch_19yUdxxxx"
}'
The above command returns JSON structured like this:
{
"id": 1412133,
"date": "2021-09-24",
"payment_method": "credit_card",
"amount_cents": 999,
"url": "https://ACCOUNT_NAME.quadernoapp.com/api/invoices/2009439/payments/1412133.json"
}
When trying to register an
amount
higher than the money pending to be paid on an invoice, you'll get an error like:
{
"errors": {
"amount": [
"must be less than or equal to €109.09" // that import is the money pending to be paid on the invoice
]
}
}
HTTP Request
POST /invoices/INVOICE_ID/payments
POST /expenses/EXPENSE_ID/payments
POST /credits/CREDIT_ID/payments
Parameters
Parameter | Type | Description |
---|---|---|
amount | decimal | Amount paid. Required |
date | date | The date of the payment . Defaults to today. |
payment_method | string | One of the following: credit_card , cash , wire_transfer , direct_debit , check , iou , paypal or other . Required |
payment_processor | string | Payment processor that you used to process the payment. |
payment_processor_id | string | The transaction ID that the payment processor assigned to the payment. |
Delete a payment
curl https://ACCOUNT_NAME.quadernoapp.com/api/invoices/INVOICE_ID/payments/PAYMENT_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /invoices/INVOICE_ID/payments/PAYMENT_ID
DELETE /expenses/EXPENSE_ID/payments/PAYMENT_ID
DELETE /credits/CREDIT_ID/payments/PAYMENT_ID
CHECKOUT
Sessions
A Checkout Session represents your customer's session as they pay for one-time purchases or subscriptions through Quaderno Checkout.
Session objects transition through the following statuses: pending
, processing
, abandoned
, failed
, and completed
. Abandoned
sessions are automatically removed after seven days of inactivity.
After you create a Session, you may want to subscribe to its webhook to fulfill the order, learn more about this on this guide.
Create a Session
A Checkout Link is a permanent URL that spans a new Checkout Session for one-time purchases or subscriptions, like a template for creating your own online buying forms. With this API you can programatically create Checkout Sessions on the fly.
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions \
-u YOUR_API_KEY:x \
-X POST \
--header 'Content-Type: application/json' \
--data '{
"payment_methods": ["card"],
"success_url": "https://example.com/success",
"items": [{
"product": "9969",
"amount": 100.0,
"name": "A product",
"description": null,
"currency": "USD",
"quantity": 1
}],
"cancel_url": "https://example.com/canceled",
"custom": {
"stripe": {
"metadata": {
"notify_url": "xxx",
"user_id": 999
}
}
},
"billing_details_collection": "auto",
"customer": {
"billing_city": "New York",
"billing_country": "US",
"billing_postal_code": "10128",
"billing_street_line_1": "E 90th Street",
"email": "anemail@example.com",
"first_name": "Susan",
"last_name": "Sanders",
"tax_id": "123456789"
}
}'
The above command returns JSON structured like this:
{
"id": 12901,
"status": "pending",
"billing_details_collection": "auto",
"cancel_url": "https://example.com/canceled",
"coupon_collection": false,
"locale": "auto",
"payment_methods": [
"card"
],
"success_url": "https://example.com/success",
"custom": {
"stripe": {
"metadata": {
"notify_url": "xxx",
"user_id": "999"
}
}
},
"metadata": {},
"processor": null,
"processor_id": null,
"items": [
{
"product": "9969",
"amount": 100.0,
"name": "A product",
"description": null,
"currency": "USD",
"quantity": 1
}
],
"customer": {
"billing_city": "New York",
"billing_country": "US",
"billing_postal_code": "10128",
"billing_street_line_1": "E 90th Street",
"billing_street_line_2": null,
"company": null,
"email": "anemail@example.com",
"first_name": "Susan",
"last_name": "Sanders",
"tax_id": "123456789",
"business_number": null
},
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/checkout/session/xxxx"
}
HTTP Request
POST /checkout/sessions
Parameters
Parameter | Type | Description |
---|---|---|
billing_details_collection |
string | The value for whether Checkout collected the customer’s billing details. Values are auto and required (default to required ). |
cancel_url |
storing | The URL the customer will be redirected to if they decide to cancel the payment and return to your website. Required |
coupon_collection |
boolean | The value for whether Checkout collected coupons. |
custom |
object | Set of key-value pairs that you want to forward to the payment processor. This can be useful for setting up additional options in the payment processor. If you want to send specific data to one processor, you can create a subhash with the name of that particular processor. E.g.: stripe: { metadata: { user_id: 999 } }, paypal: { no_shipping: 0 } |
customer |
object | The customer's billing information. Check the attributes here. |
items |
array | The list of products purchased by the customer. Check the attributes here. Required |
locale |
string | The 2-letter ISO code of the language the Checkout is displayed in. Values are auto , ca , de , en , es , fi , fr , hu , nl , sv , and no . |
metadata |
object | Set of key-value pairs that you can attach to the session. This can be useful for storing additional information about the purchase. |
payment_methods |
array | Can be card or paypal . |
success_url |
string | The URL the customer will be redirected to after the payment is successful. Required |
Here are the parameters for the Session's children objects:
Customer object parameters:
Parameter | Type | Description |
---|---|---|
billing_city |
string | City/District/Suburb/Town/Village. Use this parameter to prefill customer data if you already have her city on file. |
billing_country |
string | 2-letter country code. Use this parameter to prefill customer data if you already have her country on file. |
billing_postal_code |
string | ZIP or postal code. Use this parameter to prefill customer data if you already have her postal code on file. |
billing_street_line_1 |
string | Address line 1 (Street address/PO Box). Use this parameter to prefill customer data if you already have her street address on file. |
billing_street_line_2 |
string | Address line 2 (Apartment/Suite/Unit/Building). Use this parameter to prefill customer data if you already have her street address on file. |
contact |
integer | The ID of the Quaderno Contact. A new contact will be created unless an existing contact was provided in when the Checkout was created. |
company |
string | Customer company. Use this parameter to prefill customer data if you already have her company name on file. |
email |
string | Customer email. Use this parameter to prefill customer data if you already have an email on file. |
first_name |
string | Customer first name. Use this parameter to prefill customer data if you already have her first name on file. |
last_name |
string | Customer last name. Use this parameter to prefill customer data if you already have her last name on file |
tax_id |
string | Customer Tax ID. Use this parameter to prefill customer data if you already have a tax id on file. |
Items object parameters:
Parameter | Type | Description |
---|---|---|
amount |
number(decimal) | Unit price of the item being purchased. Defaults to the product's price is used if amount is not set. |
currency |
string | Three-letter ISO currency code, in uppercase. Must be a supported currency by the payment processor. Defaults to the product's currency is used if currency is not set. |
description |
string | The description of the item being purchased. Defaults to the product's description is used if description is not set. |
name |
string | The name of the item being purchased. Defaults to the product's name is used if name is not set. |
product |
string | The SKU of the Quaderno Product (code parameter from /items ). You need to create it first or use an existing one, or you'll get a {"errors": {"items": ["Product code xxx is not defined"]}} error. Required |
quantity |
integer | Quantity of the item being purchased. Minimum value is 1. |
Retrieve a session
Retrieves the details of an existing session. You only need to supply the unique session identifier that was returned upon session creation.
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions/SESSION_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id":1,
"status":"completed",
"billing_details_collection":"required",
"cancel_url":"http://go.back.com",
"coupon_collection":true,
"locale":"auto",
"payment_methods":["card", "paypal"],
"success_url":"http://success.com?prod=1",
"custom":{},
"items":[
{
"product":"prod_61ffa845b4a0b8",
"amount":999.0,
"name":"A test item",
"description":"This a test item.",
"currency":"EUR",
"quantity":1
}
],
"customer":{
"billing_city":"London",
"billing_country":"GB",
"billing_postal_code":"E1 6AN",
"billing_street_line_1":"A street name",
"billing_street_line_2":"",
"company":"",
"email":"john@doe.com",
"first_name":"John",
"last_name":"Doe",
"tax_id":null
},
"metadata": {},
"permalink":"https://ACCOUNT_NAME.quadernoapp.com/checkout/session/xxx"
}
HTTP Request
GET /checkout/sessions/SESSION_ID
Parameters
Parameter | Type | Description |
---|---|---|
SESSION_ID |
integer | The ID of the session to retrieve. Required |
Update a session
Updates the specified session by setting new values for the parameters passed. Any parameters not provided will be left unchanged. Only pending
sessions can be updated.
This request accepts mostly the same arguments as the session creation call, please refer to it for checking the accepted parameters.
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions/SESSION_ID \
-u YOUR_API_KEY:x \
-X PUT \
--header 'Content-Type: application/json' \
--data '{
"success_url": "https://example.com/new_success",
"metadata": {
"your_custom_metadata": "xxx"
}
}'
The above command returns JSON structured like this:
{
"id": 12901,
"status": "pending",
"billing_details_collection": "auto",
"cancel_url": "https://example.com/canceled",
"coupon_collection": false,
"locale": "auto",
"payment_methods": [
"card"
],
"success_url": "https://example.com/new_success", // updated data included in the request
"custom": {
"stripe": {
"metadata": {
"notify_url": "xxx",
"user_id": "999"
}
}
},
"metadata": { // new data included in the request
"your_custom_metadata": "xxx"
},
"processor": null,
"processor_id": null,
"items": [
{
"product": "9969",
"amount": 100.0,
"name": "A product",
"description": null,
"currency": "USD",
"quantity": 1
}
],
"customer": {
"billing_city": "New York",
"billing_country": "US",
"billing_postal_code": "10128",
"billing_street_line_1": "E 90th Street",
"billing_street_line_2": null,
"company": null,
"email": "anemail@example.com",
"first_name": "Susan",
"last_name": "Sanders",
"tax_id": "123456789",
"business_number": null
},
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/checkout/session/xxxx"
}
HTTP Request
PUT /checkout/sessions/SESSION_ID
Parameters
Please refer to Create a Session for checking the accepted parameters.
Delete a session
Permanently deletes a session. It cannot be undone. Only pending
sessions can be deleted.
$ curl https://quadernoapp.com/api/checkout/sessions/SESSION_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /checkout/sessions/SESSION_ID
Parameters
Parameter | Type | Description |
---|---|---|
SESSION_ID |
integer | The ID of the session to delete. Required |
List all sessions
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/sessions \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 1,
"status": "completed",
"billing_details_collection": "required",
"cancel_url": "https://example.com/canceled",
"coupon_collection": false,
"locale": "auto",
"payment_methods": [
"card"
],
"success_url": "https://example.com/success",
"custom": {},
"metadata": {},
"processor": "stripe",
"processor_id": "pi_xxxx",
"items": [
{
"product": "prod_226500ed1edc5c",
"amount": 10000.0,
"name": "A digital service",
"description": null,
"currency": "USD",
"quantity": 1
}
],
"customer": {
"billing_city": "New York",
"billing_country": "US",
"billing_postal_code": "10128",
"billing_street_line_1": "E 90th Street",
"billing_street_line_2": "",
"company": "",
"email": "anemail@example.com",
"first_name": "Susan",
"last_name": "Sanders",
"tax_id": "123456789",
"business_number": null
},
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/checkout/session/xxxx"
}
]
You can list all sessions, or list the sessions for a specific status. The sessions are returned sorted by creation date, with the most recently created sessions appearing first.
Returns an array of sessions. Each entry in the array is a separate session object. If no more sessions are available, the resulting array will be empty.
HTTP Request
GET /checkout/sessions
Parameters
Parameter | Type | Description |
---|---|---|
status |
string | A case-sensitive filter on the list based on the session's status. Can be pending , processing , abandoned , failed , and completed . |
Coupons
A coupon contains information about a percent-off or amount-off discount you might want to apply to a customer during a checkout.
Create a coupon
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/coupons \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"code": "SALES",
"name": "",
"percent_off": 20,
"max_redemptions": 100,
"redeem_by": "2021-11-01"
}'
The above command returns JSON structured like this:
{
"id": 2,
"code": "SALES",
"name": "",
"percent_off": 20,
"times_redeemed": null,
"processor_id": "SALES",
"amount_off_cents": null,
"currency": null,
"max_redemptions": 100,
"redeem_by": "2021-11-01"
}
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/coupons \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"code": "FINAL",
"amount_off": 10,
"currency": "EUR",
"max_redemptions": 10,
"redeem_by": "2021-12-01"
}'
The above command returns JSON structured like this:
{
"id": 3,
"code": "FINAL",
"name": null,
"percent_off": null,
"times_redeemed": null,
"processor_id": "FINAL",
"amount_off_cents": 1000,
"currency": "EUR",
"max_redemptions": 10,
"redeem_by": "2021-12-01"
}
HTTP Request
POST /checkout/coupons
Parameters
Parameter | Type | Description |
---|---|---|
code |
string | The coupon's code. Required |
name |
string | Name of the coupon displayed to customers on invoices. |
percent_off |
decimal | Percent that will be taken off the subtotal of the purchase. For example, a coupon with percent_off of 50 will make a $100 purchase $50 instead. Required if amount_off is not passed. |
amount_off |
decimal | Amount (in the currency specified) that will be taken off the subtotal of the purchase. Required if percent_off is not passed. |
currency |
string | If amount_off has been set, the three-letter ISO currency code of the amount to take off, in uppercase. Must be a supported currency in your payment processors. |
redeem_by |
date | Date after which the coupon can no longer be redeemed. |
max_redemptions |
integer | Maximum number of times this coupon can be redeemed, in total, across all customers, before it is no longer valid. |
Retrieve a coupon
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/coupons/COUPON_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 8888,
"code": "awesome",
"name": "Awesome discount",
"percent_off": 50,
"times_redeemed": null,
"processor_id": "awesome01",
"amount_off_cents": null,
"currency": null,
"max_redemptions": null,
"redeem_by": null
}
HTTP Request
GET /checkout/coupons/COUPON_ID
Parameters
Parameter | Type | Description |
---|---|---|
COUPON_ID |
integer | The ID of the coupon to retrieve. Required |
Update a coupon
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/coupons/COUPON_ID \
-u YOUR_API_KEY:x \
-X PUT \
-d '{ "percent_off": 25 }'
The above command returns JSON structured like this:
{
"id": 8888,
"code": "awesome",
"name": "Awesome discount",
"percent_off": 25, // updated field
"times_redeemed": null,
"processor_id": "awesome01",
"amount_off_cents": null,
"currency": null,
"max_redemptions": null,
"redeem_by": null
}
Note that if you're using our Stripe integration, coupons are linked to Stripe coupons. Coupon edition won't be allowed as Stripe doesn't allow editions to their coupons.
HTTP Request
PUT /checkout/coupons/COUPON_ID
Parameters
Parameter | Type | Description |
---|---|---|
COUPON_ID |
integer | The ID of the coupon to update. Required |
code |
string | The coupon's code. Required |
name |
string | Name of the coupon displayed to customers on invoices. |
percent_off |
decimal | Percent that will be taken off the subtotal of the purchase. For example, a coupon with percent_off of 50 will make a $100 purchase $50 instead. Required if amount_off is not passed. |
amount_off |
decimal | Amount (in the currency specified) that will be taken off the subtotal of the purchase. Required if percent_off is not passed. |
currency |
string | If amount_off has been set, the three-letter ISO currency code of the amount to take off, in uppercase. Must be a supported currency in your payment processors. |
redeem_by |
date | Date after which the coupon can no longer be redeemed. |
max_redemptions |
integer | Maximum number of times this coupon can be redeemed, in total, across all customers, before it is no longer valid. |
Delete a coupon
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/coupons/COUPON_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /checkout/coupons/COUPON_ID
Parameters
Parameter | Type | Description |
---|---|---|
COUPON_ID |
integer | The ID of the coupon to delete. Required |
List all coupons
GET /checkout/coupons
curl https://ACCOUNT_NAME.quadernoapp.com/api/checkout/coupons \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 1,
"code": "FALL21",
"name": "",
"percent_off": 20,
"times_redeemed": null,
"processor_id": "FALL21",
"amount_off_cents": null,
"currency": null,
"max_redemptions": 100,
"redeem_by": "2021-11-01"
},
{
"id": 2,
"code": "SALES",
"name": "",
"percent_off": 20,
"times_redeemed": null,
"processor_id": "SALES",
"amount_off_cents": null,
"currency": null,
"max_redemptions": 100,
"redeem_by": "2021-11-01"
},
{
"id": 3,
"code": "FINAL",
"name": null,
"percent_off": null,
"times_redeemed": null,
"processor_id": "FINAL",
"amount_off_cents": 1000,
"currency": "EUR",
"max_redemptions": 10,
"redeem_by": "2021-12-01"
}
]
GET
ting from /checkout/coupons
will return all the account's coupons.
CONNECT
Are you a marketplace platform? Quaderno Connect provides tax management and invoicing capabilities to your business model! Go check this step-by-step guide for details on using Quaderno Apps with Quaderno Connect Standard accounts.
Accounts (beta)
When using Quaderno Connect with custom accounts, you need to create an account (known as a connected account) for each user that needs to calculate taxes on your platform. These accounts are generally created when a user signs up for your platform. You can read more here.
Create an account
curl --request POST \
--url https://ACCOUNT_NAME.quadernoapp.com/api/accounts/ \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"business_name": "Big Bang Inc.",
"email": "bigbang@example.com",
"country": "US",
"postal_code": "10001",
"local_tax_id": "123456789",
"default_product_type": "service",
"default_tax_code": "ebook"
}'
The above command returns JSON structured like this:
{
"id": 21791,
"authentication": { // to be used to act on behalf of the new account
"token_type": "Bearer",
"access_token": "xxx", // expires on 25 days
"refresh_token": "yyy" // does not expire on Custom accounts
},
"business_name": "Big Bang Inc.",
"country": "US",
"created_at": 1632212259,
"currency": "USD",
"default_product_type": "service",
"default_tax_code": "ebook",
"email": "bigbang@example.com",
"local_tax_id": "123456789",
"state": "active",
"subdomain": "ninive-xxxx", // the new ACCOUNT_NAME for your merchant
"type": "custom"
}
HTTP Request
POST /accounts
Parameters
Parameter | Type | Description |
---|---|---|
business_name |
string | The account's business name. Required |
email |
string | The account's email address. Required |
country |
string | The account's country. 2-letter country code. Required |
postal_code |
string | The account's postal code. Recommended whether the account is based in the US or Canada to determine the state/province. |
currency |
string | The account's accounting currency. Defaults to the country's official currency. |
local_tax_id |
string | The account's tax ID. If set, the account will be registered for tax collection in the country 's jurisdiction. |
default_product_type |
string | The account's default product type to be used when no product type is specified on documents and tax calculations. Can be either good or service . Defaults to service . |
default_tax_code |
string | The account's default product type to be used when no tax code is specified on items and tax calculations. Can be any tax_code . Defaults to eservice . |
Retrieve an account
curl https://ACCOUNT_NAME.quadernoapp.com/api/accounts/ACCOUNT_ID \
-u YOUR_API_KEY:x \
The above command returns JSON structured like this:
{
"id": 6,
"business_name": "Big Bang Inc.",
"country": "US",
"created_at": 1632213997,
"currency": "USD",
"default_product_type": "service",
"default_tax_code": "ebook",
"email": "bigbang@example.com",
"local_tax_id": null,
"state": "active",
"subdomain": "assur-8680",
"type": "custom"
}
HTTP Request
GET /accounts/ACCOUNT_ID
Parameters
Parameter | Type | Description |
---|---|---|
ACCOUNT_ID |
integer | The ID of the account to retrieve. Required |
Update an account
curl https://ACCOUNT_NAME.quadernoapp.com/api/accounts/ACCOUNT_ID \
-u YOUR_API_KEY:x \
-X PUT \
-d '{ "business_name": "Another Name Inc." }'
The above command returns JSON structured like this:
{
"id": 6,
"business_name": "Another Name Inc.",
"country": "US",
"created_at": 1632213997,
"currency": "USD",
"default_product_type": "service",
"default_tax_code": "ebook",
"email": "bigbang@example.com",
"local_tax_id": null,
"state": "active",
"subdomain": "assur-8680",
"type": "custom"
}
HTTP Request
PUT /accounts/ACCOUNT_ID
Parameters
Parameter | Type | Description |
---|---|---|
ACCOUNT_ID |
integer | The ID of the account to retrieve. Required |
business_name |
string | The accounts's business name. |
email |
string | The account's email address. |
default_product_type |
string | The account's default product type to be used when no product type is specified on documents and tax calculations. Can be either good or service . Defaults to service . |
default_tax_code |
string | The account's default product type to be used when no tax code is specified on items and tax calculations. Can be any tax_code . Defaults to eservice . |
List all accounts
curl https://ACCOUNT_NAME.quadernoapp.com/api/accounts \
-u YOUR_API_KEY:x
The above command returns a JSON list structured like this:
[
{
"id": 6,
"business_name": "Big Bang Inc.",
"country": "US",
"created_at": 1632213997,
"currency": "USD",
"default_product_type": "service",
"default_tax_code": "ebook",
"email": "bigbang@example.com",
"local_tax_id": null,
"state": "active",
"subdomain": "assur-8680",
"type": "custom"
},
…
]
HTTP Request
GET /accounts
TRANSACTIONS
curl https://ACCOUNT_NAME.quadernoapp.com/api/transactions \
-u YOUR_API_KEY:x \
-X POST \
--header 'Content-Type: application/json' \
--data '{
"type": "sale",
"customer": {
"first_name": "John",
"last_name": "Smith",
"email": "an_email@example.com"
},
"evidence": {
"billing_country": "US",
"ip_address": "255.255.255.255",
"bank_country": "US"
},
"currency": "USD",
"items": [
{
"description": "Awesome ebook",
"quantity": 1,
"amount": 19,
"tax": {
"city": null,
"country": "US",
"county": null,
"currency": "USD",
"name": null,
"notes": null,
"notice": "",
"product_type": "service",
"rate": 0.0,
"region": "NY",
"tax_behavior": "exclusive",
"tax_code": "ebook",
"taxable_part": null,
"import": true,
"subtotal": null,
"tax_amount": null,
"total_amount": null
}
},
{
"description": "Awesome SaaS",
"quantity": 1,
"amount": 89,
"tax": {
"city": "NEW YORK",
"country": "US",
"county": "NEW YORK",
"currency": "USD",
"name": "Sales tax",
"notes": null,
"notice": "",
"product_type": "service",
"rate": 8.875,
"region": "NY",
"tax_behavior": "exclusive",
"tax_code": "saas",
"taxable_part": 100.0,
"import": true,
"subtotal": null,
"tax_amount": null,
"total_amount": null
}
}
],
"payment": {
"method": "credit_card",
"processor": "aPaymentProcessor",
"processor_id": "ch_xxxxx"
},
"processor": "your-user-agent",
"processor_id": "your-internal-request-id", // needed for later refunds
"po_number": "PO_xxxxxx",
"tags": "tag-a,tag-b,tag-c",
"custom_metadata": {
"anything_you_want": "extra info"
}
}'
The above command returns JSON structured like this:
{
"id": 2008034,
"contact": {
"id": 1248822,
"city": null,
"country": "ES",
"created_at": 1631884113,
"email": "an_email@example.com",
"first_name": "John",
"full_name": "John Smith",
"kind": "person",
"language": "ES",
"last_name": "Smith",
"notes": null,
"permalink": "https://ACCOUNT_NAME.quadernoapp.com/billing/th3p3rm4l1nk?otp=xxx",
"phone_1": null,
"postal_code": null,
"processor": null,
"processor_id": null,
"region": null,
"street_line_1": null,
"street_line_2": null,
"tax_id": null,
"web": null
},
"created_at": 1631884142,
"currency": "USD",
"custom_metadata": {
"anything_you_want": "extra info"
},
"discount_cents": 0,
"evidence": {
"bank_country": "US",
"billing_country": "US",
"created_at": 1631884142,
"ip_address": "255.255.255.255",
"ip_country": null,
"state": "confirmed"
},
"issue_date": "2021-09-17",
"items": [
{
"id": 5888097,
"created_at": 1631884142,
"description": "Awesome ebook",
"discount_cents": 0,
"discount_rate": 0.0,
"product_code": null,
"quantity": 1.0,
"subtotal_cents": 1900,
"total_amount_cents": 1900,
"unit_price": 19.0
},
{
"id": 5888098,
"created_at": 1631884142,
"description": "Awesome SaaS",
"discount_cents": 0,
"discount_rate": 0.0,
"product_code": null,
"quantity": 1.0,
"subtotal_cents": 8175,
"tax_code": "saas",
"tax_country": "US",
"tax_name": "Sales tax",
"tax_rate": 8.875,
"tax_region": "NY",
"taxable_part": "100.0",
"total_amount_cents": 8900,
"unit_price": 81.75
}
],
"notes": null,
"number": "00019",
"payments": [
{
"id": 1410566,
"amount_cents": 10800,
"created_at": 1631884142,
"date": "2021-09-17",
"payment_method": "credit_card",
"processor": "aPaymentProcessor", // Payment processor
"processor_id": "ch_xxxxx"
}
],
"pdf": "https://quadernoapp.com/invoice/th3p3rm4l1nk.pdf?otp=xxx&q=yyy",
"permalink": "https://quadernoapp.com/invoice/th3p3rm4l1nk?otp=xxx&q=yyy",
"po_number": "PO_xxxxxx",
"processor": "my_api_user_agent", // Transaction processor (your user-agent)
"processor_id": "id_to_reference_transaction", // reference this when creating a type: refund
"state": "outstanding",
"subtotal_cents": 10075,
"tags": "tag-a, tag-b, tag-c",
"taxes": [
{
"amount_cents": 725,
"country": "US",
"label": "Sales tax (8.875%)",
"rate": 8.875,
"region": "NY",
"tax_code": "saas"
}
],
"total_cents": 10800,
"type": "Invoice"
}
HTTP Request
POST /transactions
The Transactions API is perfect for use cases where your customers pay you in real time:
- First call the tax calculation endpoint to show your sale's taxes on your checkout.
- Then use your payment processor APIs to complete the payment.
- Finally, create a
Transaction
object to easily send the completed sale from your platform to Quaderno.
We'll use these information to issue invoices & credit notes, as well as update tax reports automatically. This API can also be used together with Quaderno Connect by using the account bearer token.
Parameter | Type | Description |
---|---|---|
type |
string | The transaction's type. Can be sale and refund . Defaults to sale . For refunds, you need to send the processor_id used on the sale transaction. |
date |
date | The transaction's date. Defaults to today. |
customer |
object | The data of the customer who paid the transaction. You can reference an existing contact by its id, or pass the json object to create a new contact. |
shipping_address |
object | The mailing address to where the order will be shipped. Use it if the order contains physical goods. |
currency |
string | Three-letter ISO currency code, in uppercase. Defaults to the account's default currency. |
items |
array | The individual items that make up the transaction. See parameters below. Required |
payment |
object | Detailed information about the transaction payment. See parameters below. Required |
processor |
string | The name of the platform that processed the transaction. E.g. shopify, woocommerce or any user agent you may want to use to identify yourself… Recommended |
processor_id |
string | The ID of the transaction in the processor . Reference this when creating a transaction of type: refund , so that the resultant credit note is associated to the invoice. Recommended |
evidence |
object | Location evidence of the customer's residence. See parameters below. Highly recommended |
po_number |
string | The number of the related order. Recommended |
tags |
string | Tags attached to the transaction, formatted as a string of comma-separated values. Tags are additional short descriptors, commonly used for filtering and searching. Each individual tag is limited to 40 characters in length. |
notes |
string | Optional notes attached to the transaction. |
custom_metadata |
hash | Set of key-value pairs that you can attach to an object. This can be useful for storing additional information about the object in a structured format. You can have up to 20 keys, with key names up to 40 characters long and values up to 500 characters long. |
Items
Each item object inside the items
array can use the following parameters:
Parameter | Type | Description |
---|---|---|
product_code |
string | Code of an existing product. If present, you don't need to set the description. |
description |
string | An arbitrary string attached to the object. Often useful for displaying to users. Required. |
quantity |
integer | Quantity of units for the transaction item. Defaults to 1. |
discount_rate |
integer | This represents the discount percent out of 100 included in the amount , if applicable. |
tax |
object | The tax rates which apply to the transaction item. Use the response from the tax calculation endpoint. |
amount |
decimal | Total amount (in the currency specified) of the transaction item. This should always be equal to the amount charged after discounts and taxes. Required. |
Payments
Parameter | Type | Description |
---|---|---|
method |
string | The payment method used to pay the transaction. Can be credit_card , cash , wire_transfer , direct_debit , check , iou , paypal or other Defaults to other . |
processor |
string | The name of the payment processor used to take the payment. E.g. stripe, paypal… |
processor_id |
string | The ID of the transaction in the processor . |
Evidences
Parameter | Type | Description |
---|---|---|
billing_country |
string | Customer's billing country (2-letter ISO code) |
ip_address |
string | Customer's IP address |
bank_country |
string | Customer's bank country (2-letter ISO code) |
Refunds
To create a refund using the Transactions API, please use type: refund
plus the same processor_id
that you used when registering your sale.
REPORTING
Report Requests
Every time you need to generate a Quaderno report, you need to create a report request with specific parameters. When the report has finished running, GET
ting the request id
will give you the reference to the file where you can retrieve your results.
Create a request
curl https://ACCOUNT_NAME.quadernoapp.com/api/reporting/requests \
-u YOUR_API_KEY:x \
--header 'Content-Type: application/json' \
--data '{
"report_type": "tax_summary",
"parameters": {
"from_date": "2021-09-01",
"to_date": "2021-09-30"
}
}'
The above command returns JSON structured like this:
{
"id": 8, // use the request id to get the final file later
"created_at": 1632988001,
"parameters": {
"from_date": "2021-09-01",
"to_date": "2021-09-30"
},
"report_type": "tax_summary",
"report_url": null, // always null at first
"state": "pending"
}
HTTP Request
POST /reporting/requests
Parameters
Parameter | Type | Description |
---|---|---|
report_type |
string | The type of report. Can be tax_summary , invoices_list or credits_list . Required |
parameters |
string | Parameters specifying how the report should be run. Different report types have different required and optional parameters. |
Parameters for tax summary, invoices list, and credits list:
Parameter | Type | Description |
---|---|---|
from_date |
date | Starting date of data to be included in the report. Required |
to_date |
date | Ending date of data to be included in the report. Required |
Retrieve a request
After a while from creating the report request, the file will be ready. Use the request id
to get it.
curl https://ACCOUNT_NAME.quadernoapp.com/api/reporting/requests/REQUEST_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 41,
"completed_at": 1633519917,
"created_at": 1633519917,
"parameters": {
"from_date": "2021-09-01",
"to_date": "2021-09-30"
},
"report_type": "tax_summary",
"report_url": "https://quaderno.s3.amazonaws.com/reports/41/xxxxxxx",
"state": "succeeded"
}
HTTP Request
GET /reporting/requests/REQUEST_ID
Parameters
Parameter | Type | Description |
---|---|---|
REQUEST_ID |
integer | The ID of the request to retrieve. Required |
List all requests
curl https://ACCOUNT_NAME.quadernoapp.com/api/reporting/requests \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 1,
"completed_at": 1612351337,
"created_at": 1612351337,
"parameters": {
"from_date": "2020-10-01",
"to_date": "2020-12-31"
},
"report_type": "tax_summary",
"report_url": "https://quaderno.s3.amazonaws.com/reports/xxxxxxxxx",
"state": "succeeded"
},
{
"id": 32,
"created_at": 1632993561,
"error": "invalid date", // this report failed because september has 30 days, not 31
"parameters": {
"from_date": "2021-09-01",
"to_date": "2021-09-31"
},
"report_type": "tax_summary",
"report_url": null,
"state": "failed"
}
]
HTTP Request
GET /reporting/requests
WEBHOOKS
Webhooks Endpoints
Webhooks refers to a combination of elements that collectively create a notification and reaction system within a larger integration.
Metaphorically, webhooks are like a phone number that Quaderno calls to notify you of activity in your Quaderno account. The activity could be the creation of a new customer or the payment of an invoice. The webhook endpoint is the person answering that call who takes actions based upon the specific information it receives.
Non-metaphorically, the webhook endpoint has an associated URL (e.g., https://example.com/webhooks
) and is just more code on your server, which could be written in Ruby, PHP, Node.js, or whatever.
The Quaderno notifications are Event
objects. This Event
object contains all the relevant information about what just happened, including the type of event and the data associated with that event. The webhook endpoint uses the event details to take any required actions, such as indicating that an order should be fulfilled.
Create a webhook
curl https://ACCOUNT_NAME.quadernoapp.com/api/webhooks \
-u YOUR_API_KEY:x \
-d '{
"url": "https://webhook.site/6e76b64d-584c-4910-8ec6-xxxx",
"events_types": ["invoice.created", "invoice.updated" ]
}'
The above command returns JSON structured like this:
{
"id": 2156,
"url": "https://webhook.site/6e76b64d-584c-4910-8ec6-xxxx",
"auth_key": "xxxx",
"events_types": [
"invoice.created",
"invoice.updated"
],
"last_sent_at": null,
"last_error": null,
"events_sent": 0,
"created_at": "2021-09-30T07:57:34.000Z",
"updated_at": "2021-09-30T07:57:34.000Z"
}
HTTP Request
POST /webhooks
Parameters
Parameter | Type | Description |
---|---|---|
url |
string | The destination URL of the webhook request. Required |
events_types |
array | An array of strings indicating which events you wish to subscribe to. Check the available event types here. Required |
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 a webhook
GET /webhooks/WEBHOOK_ID.json
curl https://ACCOUNT_NAME.quadernoapp.com/api/webhooks/WEBHOOK_ID \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
{
"id": 3,
"url": "http://anotherapp.com/notifications",
"auth_key": "xxxx",
"events:_types": ["invoice.created","contact.deleted"],
"last_sent_at": "2021-10-06T11:43:44.000Z",
"last_error": null,
"events_sent": 10,
"created_at": "2013-07-13T14:12:01Z",
"updated_at": "2013-07-17T14:09:59Z"
}
HTTP Request
GET /webhooks/WEBHOOK_ID
Parameters
Parameter | Type | Description |
---|---|---|
WEBHOOK_ID |
integer | The ID of the webhook to retrieve. Required |
Update a webhook
curl https://ACCOUNT_NAME.quadernoapp.com/api/webhooks/WEBHOOK_ID \
-u YOUR_API_KEY:x \
-X PUT \
-d '{
"url": "https://webhook.site/6e76b64d-584c-4910-8ec6-xxxx",
"events_types": ["contact.updated", "contact.created" ]
}'
The above command returns JSON structured like this:
{
"id": 3,
"url": "http://anotherapp.com/notifications",
"auth_key": "xxxx",
"events:_types": ["contact.updated", "contact.created"],
"last_sent_at": null,
"last_error": null,
"events_sent": null,
"created_at": "2013-07-13T14:12:01Z",
"updated_at": "2013-07-17T14:09:59Z"
}
HTTP Request
PUT /webhooks/WEBHOOK_ID
Parameters
Parameter | Type | Description |
---|---|---|
WEBHOOK_ID |
integer | The ID of the webhook to retrieve. Required |
url |
string | The destination URL of the webhook request. Required |
events_types |
array | An array of strings indicating which events you wish to subscribe to. Check the available event types here. Required |
Delete a webhook
curl https://ACCOUNT_NAME.quadernoapp.com/api/webhooks/WEBHOOK_ID \
-u YOUR_API_KEY:x \
-X DELETE
HTTP Request
DELETE /webhooks/WEBHOOK_ID
Parameters
Parameter | Type | Description |
---|---|---|
WEBHOOK_ID |
integer | The ID of the webhook to delete. Required |
List all webhooks
curl https://ACCOUNT_NAME.quadernoapp.com/api/webhooks \
-u YOUR_API_KEY:x
The above command returns JSON structured like this:
[
{
"id": 2,
"url": "https://myawesomeapp.com/notifications",
"auth_key": "xxxx",
"events": ["contact.created", "contact.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": "xxxx",
"events:_types": ["invoice.created", "contact.deleted"],
"last_sent_at": "2021-10-06T11:43:44.000Z",
"last_error": null,
"events_sent": 10,
"created_at": "2013-07-13T14:12:01Z",
"updated_at": "2013-07-17T14:09:59Z"
}
]
HTTP Request
GET /webhooks
Webhook payload examples
{
"event_type": "invoice.created",
"account_id": 21417,
"data": {
"object": {
"evidence": null,
"id": 2012730,
"contact_id": 1247386,
"tag_list": [],
"number": "00023",
"issue_date": "2021-10-06",
"contact_name": "John",
"currency": "EUR",
"gross_amount_cents": 9910,
"total_cents": 11800,
"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",
"tax_id": null,
"processor_id": null,
"processor": null,
"processor_fee_cents": null,
"custom_metadata": {},
"due_date": null,
"permalink": "a760b05e7fa068792b8c0f6dcc04b2b8ce856960",
"contact": {
"first_name": "John",
"last_name": null,
"email": null,
"contact_person": null
},
"gross_amount": "99.10",
"total": "118.00",
"amount_paid": "0.00",
"exchange_rate": "1.000000",
"items": [
{
"id": 5894250,
"product_code": "prod_ebook1",
"description": "How to live with taxes",
"quantity": "1.0",
"unit_price": "9.1",
"discount_rate": "0.0",
"tax_1_amount_cents": 0,
"tax_1_name": "",
"tax_1_rate": 0,
"tax_1_country": "",
"tax_2_amount_cents": 0,
"tax_2_name": "",
"tax_2_rate": 0,
"tax_2_country": "",
"subtotal_cents": "910.0",
"total_amount_cents": 910,
"discount_cents": "0.0",
"taxes_included": false,
"reference": "prod_ebook1",
"tax_1_amount": "0.00",
"tax_2_amount": "0.00",
"unit_price_cents": "910.00",
"discount": "0.00",
"subtotal": "9.10",
"total_amount": "9.10"
},
{
"id": 5894251,
"product_code": "prod_zzz",
"description": "A new product example",
"quantity": "1.0",
"unit_price": "100.0",
"discount_rate": "10.0",
"tax_1_amount_cents": 1890,
"tax_1_name": "IVA",
"tax_1_rate": 21,
"tax_1_country": "ES",
"tax_2_amount_cents": 0,
"tax_2_name": "",
"tax_2_rate": 0,
"tax_2_country": "",
"subtotal_cents": "10000.0",
"total_amount_cents": 10890,
"discount_cents": "1000.0",
"taxes_included": false,
"reference": "prod_zzz",
"tax_1_amount": "18.90",
"tax_2_amount": "0.00",
"unit_price_cents": "10000.00",
"discount": "10.00",
"subtotal": "100.00",
"total_amount": "108.90"
}
],
"payments": []
}
}
}
In this example, the user duplicated an invoice using the App, so the configured URL for receiving our Webhook responses received a POST
with a header x-quaderno-signature:
containing the auth_key
.
You can see the whole body payload of its related invoice.created
event on the right.
Another useful example would be registering for tax thresholds events, like threshold.warning
and threshold.exceeded
.
Here it is a body payload response example:
{
"event_type": "threshold.warning",
"account_id": 20988,
"data": {
"object": {
"name": "90%",
"country": "AU",
"region": null,
"postal_code": null,
"date": "2021-10-08T08:43:48.000Z"
}
}
}