# Invoicing
NOTE This is work in progress so please ask questions and give feedback to us, so we can improve the documentation and the API through the Slack channel you've been/will be invited to.
# Invoice Flow
# Prerequisites
To use the invoice creation endpoint you need an OAUTH token. See oauth-flow, preferrably Personal Access Token so your users don't have to do the authentication flow every time they are sending an invoice.
# Create invoice request
A POST request to the /v2/invoices
endpoint creates a new invoice
Row unit prices need to be expressed in the money format .
# Fields:
client_type
- COMPANY or PERSON
email
- required if delivery method is EMAIL
business_id
- required when client_type is COMPANY
company_name
- required when client_type is COMPANY, limited to 35 characters
contact_person
- required, max 35 characters
description
- Required when
notify_overdue_and_automatic_debt_collection
is set true. The description of the done work must - max: 300 characters be on the invoice. Just "work" or list of targets/addresses is not descriptive enough. The description has to tell both the client and the person processing the invoice what work is being invoiced.
- Required when
language
- FI or SV or EN
notify_overdue_and_automatic_debt_collection
- When true UKKO.fi takes care of invoice follow-up, overdue notices and debt collection.
vat_rate
- The vat_rate parameter is required to be "VAT_REGULAR_255" for 25,5% vat OR "VAT_CONSTRUCTION_REVERSE_CHARGE" for the construction industry's reverse-charge vat.
vat_rate_explanation
- Required when vat_rate is not VAT_REGULAR_255
occupation_id
- The line of work being invoiced
- NOTE: This may require a mapping between your line of work lists and UKKO.fi lists.
- NOTE: Also note that jobs requiring permits need to be handled with great scrutiny on our end as some work are not allowed to be invoiced through UKKO.fi.
- For getting occupation ids see the Getting a list of occupations for the invoice section.
term_of_payment
- minimum 14 days for person client
- minimum 7 days for company client when
notify_overdue_and_automatic_debt_collection
is false, otherwise 14 days
delivery_method
MAIL
,EMAIL
,EINVOICE
allowed for companies
einvoice_address
- required if the delivery_method is set to
EINVOICE
- required if the delivery_method is set to
zip_code
- required
street_address1
- required
country
- required
user_confirmed_work_in_finland
is required to betrue
if the client country is notFI
.clients_reference
- optional, max 35 characters
# Invoice Rows, key rows
Array of objects with properties:
short_description
- Required, max 50 characters
quantity
- 1 or more
unit_price
- Money object and as fractions of cents for extra precision, ie 5 EUR * 1000 = 5000 fractions of cents
discount_sum
- Money object
- Cannot be used with
discount_percent
discount_percent
- Numeric percentage of a discount, e.g. 10 for 10%
- Cannot be used with
discount_sum
contains_vat
- When true invoice gets calculated so that the unit price contains the VAT
unit_type
- is PIECE or HOUR
start_date
start of the work period in the defined date time formatend_date
end of the work period in the defined date time format
# Unit_price money values
Although we're handling most of the money values as integer cents, the
invoice.rows.[...].unit_price
is handled as fractions of cents, e.g.
5 EUR * 1000 = 5 000
as there are cases where the additional precision is
required for this field.
{
"amount": "5000000",
"currency": "EUR"
}
# Example request:
{
"company_name": "Example Company Oy",
"business_id": "6811327-1",
"einvoice_address": "123",
"contact_person": "John Doe the 3rd",
"client_type": "COMPANY",
"language": "FI",
"street_address1": "Testitie 5",
"zip_code": "00100",
"city": "Helsinki",
"country": "FI",
"email": "invoice-receiver@ukko.fi",
"clients_reference": "123654",
"description": "Example work description",
"occupation_id": 82,
"notify_overdue_and_automatic_debt_collection": true,
"vat_rate": "VAT_REGULAR_255",
"delivery_method": "EMAIL",
"rows": [
{
"short_description": "Description of the work that was done",
"quantity": "10.00",
"discount_sum": null,
"discount_percent": null,
"unit_price": {
"amount": "20000",
"currency": "EUR"
},
"contains_vat": true,
"unit_type": "PIECE",
"start_date": "2023-01-01T00:00:00+02:00",
"end_date": "2023-01-15T00:00:00+02:00"
}
]
}
# Example response:
{
"data": {
"id": 1,
"company_name": "string",
"contact_person": "string",
"client_type": "COMPANY",
"language": "FI",
"email": "user@example.com",
"street_address1": "string",
"zip_code": "string",
"city": "string",
"country": "string",
"business_id": "string",
"clients_reference": "string",
"description": "string",
"occupation": {
"id": 12,
"code": "96021",
"title_fi": "Parturikampaamo",
"title_en": "Hairdressing activities",
"description_fi": "Kampaaja ja parturitoiminta, hiuslisäkkeet",
"description_en": "Hairdressing and barber services, hair extensions"
},
"user_confirmed_work_in_finland": false,
"vat_rate": "VAT_REGULAR_255",
"term_of_payment": 14,
"due_date": "2023-01-15",
"reference_number": "1234567",
"barcode": "4301237300012305600120000000000000000000012345674200213",
"created_at": "2023-01-01",
"rows": [{
"id": 1,
"invoice_id": 1,
"short_description": "string",
"start_date": "string",
"end_date": "string",
"quantity": 1,
"unit_type": "PIECE",
"unit_price": {
"amount": "125500",
"currency": "EUR"
},
"contains_vat": true,
"total_sum_with_vat": {
"amount": "12550",
"currency": "EUR"
},
"total_sum_without_vat": {
"amount": "10000",
"currency": "EUR"
},
"vat_percent": 0,
"vat_sum": {
"amount": "2550",
"currency": "EUR"
},
"discount_type": null,
"discount_percent": null,
"discount_sum": null,
"total_sum_without_vat_before_discount": {
"amount": "10000",
"currency": "EUR"
},
"total_sum_with_vat_before_discount": {
"amount": "12550",
"currency": "EUR"
},
"row_type": null
}],
"can": {
"create": {
"allowed": true
},
"send": {
"allowed": false,
"reasons": [
"terms_of_service_not_signed",
"strong_identification_missing"
]
}
}
}
}
More details on the different request and return parameters can be found in the API documentation.
NOTE: If there are other can.send.reasons
than
terms_of_service_not_signed
, you'll need to tell the user to update their
UKKO.fi account settings in the https://app.ukko.fi/settings page.
# Getting a list of occupations for the invoice
To get a list of occupations you can use the occupations API. The occupations
are available with the following language codes: fi
, and en
.
# Example Request:
GET https://[UKKO_HOST]/v2/occupations?lang=en
# Example response
"Vehicle Maintenance": [
{
"id": 82
"code": "62020"
"title": "Motorcycle maintenance and repair"
"description": "Description of the occupation in English"
}
{
"id": 83
"code": "74300"
"title": "Repair of tyres"
"description": "Description of the occupation in English"
}
]
"Information Technology": [
{
"id": 85
"code": "96021"
"title": "Repair of computers and peripheral equipment"
"description": "Description of the occupation in English"
}
]
# Showing Terms of Service check
To allow for invoicing a user has to have accepted the UKKO.fi terms of service.
If the invoice response has can.send.allowed = false
and
`can.send.reasons[] = 'terms_of_service_not_signed', you'll need to show the
user the current terms of service as defined below.
# Example request
GET https://[UKKO_HOST]/v2/system/service-agreements/terms-of-service?language=fi
To get the current terms of service you can use the terms of service API. The
terms of services are available with the following language codes: fi
, and
en
.
# Example response
{
"data": {
"terms": "<h2>UKKO.fi-palvelun käyttöehdot</h2>...",
"type": "terms-of-service",
"language": "fi",
"published_at": "2020-03-03T12:44:30+02:00"
}
}
# Accepting Terms of Services
After showing the user the TOS and user accepting it. You need to send three POST request to the following endpoint to accept the terms of service.
# Example request
POST /me/service-agreement/has-confirmed-no-business-id/accept
POST /me/service-agreement/terms-of-service/accept
POST /me/service-agreement/industry-restrictions/accept
For API Partners the agreements has-confirmed-no-business-id
and
industry-restrictions
doesn't need to be shown as they are either in the
phasing out stage or needs to be added to your own Terms Of Service documents.
Please consult your service agreement with UKKO.fi for more details.
# Sending a created invoice to processing
# Send to processing request
If the Create Invoice response contains can.send.allowed
as true
, you can
send the invoice to admin processing.
A POST request to /v2/invoices/{invoice_id}/processing
sends to specified
invoice to UKKO.fi administration for processing. The endpoint returns 204
http-status code on success.
Only an invoice that is in the DRAFT
state can be sent to processing. Newly
created invoices and rejected invoices are in the DRAFT
state.
# Example request
POST https://[UKKO_HOST]/v2/invoices/12345/processing`
# What Happens If the Sent Invoice Gets Rejected
One of the benefits of using UKKO.fi is that our trained financial admins manually check and validate most of the invoices before sending the invoices to the client. This means that the invoice may get rejected (sent back to the user for fixes or for additional information) with an explanation message, why the invoice was rejected/discarded.
This means you need to currently poll about the invoice status changes to see whether it went TO SENT or REJECTED state. If it went to REJECTED, then the user needs to make the necessary changes and send the invoice to processing again.
You can read the rejection note contents from invoice response
rejection_note.message
field.
Later on there will be webhooks for getting updated about invoice status changes and rejects.
# What happens after the invoice sent to processing?
You can ask UKKO.fi to mark the invoice as paid. After that you can see the generated calculated salary in UKKO.fi SPA.
# Policy failures
NOTE: If these are not clear enough, please consult UKKO.fi API support for better descriptions:
- terms_of_service_not_signed
- missing_country_of_living
- country_of_living_not_allowed
- ssn_missing
- strong_identification_missing
- strong_identification_expired
- too_young
- guardians_approval_required
- nationality_not_set
- has_no_valid_id_document