Sovelluskehittäjille

Postita tarjoaa loistavat mahdollisuudet sovelluskehittäjille

Postita.fi API mahdollistaa PDF-tiedostojen siirtämisen palveluun tulostettaviksi ja postitettaviksi HTTP-rajapinnan avulla. API:n toiminnot sisältävät PDF-tiedostojen postittamisen sekä postitusjonon ja ennakkomaksutilin tilanteiden seurannan.

Palveluun lähetetyt PDF-tiedostot tulostetaan mustavalkoisena A4-arkille, joka asetetaan isoikkunaiseen C5-kirjekuoreen. Vastaanottajan osoite on lisättävä PDF-tiedostoon alueelle, joka näkyy kirjekuoren ikkunasta.

API:n käyttöönotto tapahtuu rekisteröitymällä Postita.fi-palveluun ja lisäämällä käyttäjällesi API-oikeudet tili ja asetukset välilehdeltä.

Postita HTTP API

This documentation is only available in English to better serve both our domestic and international customers.

Contents

  1. Contents
  2. Overview
  3. Authentication
  4. Job status
  5. Errors
  6. API
    1. send
    2. send_finvoice
    3. send_with_address
    4. confirm
    5. delete
    6. job_info
    7. job_list
    8. job_pdf
    9. account_info
  7. Examples
    1. PHP
      1. PHP with curl
      2. PHP with Zend
      3. PHP with fopen
    2. Python
    3. Ruby
    4. Java
  8. References

Overview

Postita.fi HTTP1 API enables our users to automate their use of our services, such as sending mail and electronic invoices through us. It also allows users to query their job queue status, status of individual jobs and account status.

To send mail through Postita.fi, users simply upload a PDF file to our service. We then print the PDF file on A4 sized paper in black and white, put it into a big-windowed C5 envelope and send it.

The process of sending electronic invoices is similar but the PDF file is replaced by a XML file in the Finvoice2 format. We will process it and handle the rest of the process.

These transactions are collectively known as jobs. Jobs are processed as a batch at 10:00 Central European Time each workday, and will be sent during that same day.

PDF and XML data is encoded using url safe base64 coding (RFC 4648). Base64 padding (with ’=’) is required and is available by default on most of base64 libraries. For more information about padding see RFC 4648 section 3.2. Padding of Encoded Data and the examples section of this document.)

Here is a synopsis of the functionality our API exposes:

  • Sending mail – send
  • Sending electronic invoices in Finvoice format – send_finvoice
  • Canceling a job – delete
  • Querying a job’s status – job_info
  • Querying the status of an entire job queue – job_list
  • Querying the PDF file related to a job – job_pdf
  • Querying the user’s account status (e.g. balance) – account_info

Authentication

All parts of our API require authentication. We use traditional HTTP basic access authentication3, commonly referred to as “basic auth”. The API uses the same login credentials as the web interface. The use of our API is completely free of charge.

We provide several examples of how to implement HTTP basic auth in different programming languages later in the document, under Examples.

401 Error code

401 http status code is returned if there is problem with the ”Basic auth” section of the request. Raw HTTP_AUTHORIZATION header should include ”Basic: XXXXX” where XXXXX stands for base64 encoded username:password pair. UTF-8 encoding is required if password/username includes special characters.

Test that you can login to postita.fi web interface with the same credentials and reset the password if needed. Check the code examples and contact our support (info@postita.fi) if problem persists.

Job status

Every Job in loaded to Postita.fi has one of these statuses.

Job API status code Description
DR Draft. Only for massmail.
NE New. Job has been loaded but not confirmed to be sent
CO Confirmed. Job is confirmed to be sent at 11 o’clock next work day.
CA Canceled. Job is cancelled.
PR Processing. Job currently in been processed and printed to be mailed
SE Sent. Job has been sent.

Errors

We use standard HTTP status codes to report any errors that might occur during the handling of API requests. Often the exact nature of the error is written to the response in addition to the status code — if and when errors occur this should be the first place to look at for more information.

For example, if the ’pdf’ argument to send is not properly encoded in urlsafe Base64 encoding4, the error code of the response will be 400 (Bad Request) and the content will be “Invalid urlsafe base64”.

API

send

https://postita.fi/api/send/

send is used to transmit PDF files to be printed and mailed through our service. Jobs sent through API are set automatically to ’Confirmed’ status and will be sent automatically. Funds will be automatically reserved from user’s account when send is called, and they will be charged when the job is processed unless the job is cancelled before that.

Note: All PDF files sent through this API call MUST contain address details already within them. The API has a separate call, send_with_address, that will add a cover page with a given address to the job.

It is possible to receive monthly reports from our service that contain details about the customer’s use of our service. Some of our users (account agencies for example) want to resell our services to their clients and thus need multiple different reports. The argument receiver_id exists to accommodate this need. It is not normally required, but if you’re interested in this kind of functionality don’t hesitate to contact us.

Arguments

  • job_name – The name of the job.
  • pdf – The PDF file to be printed and mailed, encoded with urlsafe Base644 encoding.
  • receiver_id (optional) – Invoice receiver ID. You can store your own ID here up to 256 characters. Monthly Invoice from Postita-service will have separate invoice row for each ID.
  • post_class (optional) (int 1-4) – Post class:
    1: 1 st. class
    2: 2nd. class
    3: 1st. class color
    4: 2nd. class color
  • pdf_splitter (optional) (int) – PDF-file will be splitted to seperate posts. Argument specifies the number of pages in each post. Example: PDF with 100 pages is sent to API with argument pdf_splitter=4. PDF is splitted to 25 separate posts with 4 pages each. (Original PDF should have an address data in the correct placement every four pages. Address placement instructions.)
  • confirm (optional) – As a default behaviour PDF files sent via API to Postita-service are always confirmed to be mailed. If argument ”false” is provided here the PDF will not be confirmed and will not be mailed automatically.

Error codes

  • 400: Bad Request – There was an error in the POST arguments.
  • 401: Unauthorized – Authentication failed.
  • 405: Method Not Allowed – send only works with POST requests.
  • 409: Conflict – Check returned message for details. Insufficient funds or problem with the argument values.
  • 415: Unsupported Media Type – There was something wrong with the

uploaded file.

send_finvoice

https://postita.fi/api/send_finvoice/

send_finvoice sends an electronic invoice in the Finvoice2 format.

By default electronic invoices are set automatically to the ’Confirmed’ status. If Einvoice address could not be verified invoice is confirmed as Snailmail. You can change this behaviour by using the use_snail_backup argument.

Sending multiple invoices embedded in a single Finvoice XML file is supported. PDF version of the Invoice is generated automatically by the service unless you provide your own PDF with argument invoice_pdf.

Arguments

  • job_name – The name of the job.
  • finvoice – The Finvoice XML file to be processed, encoded with urlsafe Base644 encoding.
  • pdf (Legacy support, use finvoice argument) – Same attribute as ’finvoice’: The Finvoice XML file to be processed, encoded with urlsafe Base644 encoding.
  • invoice_pdf (optional) – PDF version of the invoice. If invoice_pdf argument is not provided Postita will generate a PDF invoice from FinvoiceXML data. FinvoiceXML cannot include multpile invoices if ’invoice_pdf’ argument is used. PDF file is to be processed, encoded with urlsafe Base644 encoding.
  • confirm (optional) – Job is confirmed automatically as default. Job is not automatically confirmed if string value “False” is provided as value.
  • use_snail_backup (optional) – If set to false einvoices for which the einvoice address could not be verified will be set with status ’NE’ (unconfirmed to be sent). By default einvoices with unverified einvoice addresses will be confirmed as snailmail.

Error codes

  • 400: Bad Request – There was an error in the POST arguments.
  • 401: Unauthorized – Authentication failed.
  • 405: Method Not Allowed – send_finvoice only works with POST requests.
  • 409: Conflict – Check returned message for details. Insufficient funds or problem with the argument values.

Example response

[
  {
    'status': 'CO',
    'name': 'Finvoice API - Example Company Inc',
    'created': '25.05.2009 12:14:33',
    'price': '0.84',
    'total_pages': 1,
    'recipient_count': 1,
    'is_massmail': false,
    'is_einvoice': true,
    'errors' ['List of errors', 'Another error'],  /* errors is available only if there is a problem confirming the einvoice */
    'id': 70579
  }
]

send_with_address

https://postita.fi/api/send_with_address/

send_with_address is exactly like send, but with additional arguments for address details. A new blank page will be added to the front of the PDF with the address printed in correct place. This enables our users to easily send material that does not come with address overlaid.

The address arguments can be left empty, but it is always the user’s responsibility to make sure that a full address is included. Our international customers in particular need to make sure that the country line is always filled — Postita.fi is based in Finland and we need to know which country to send the mail to. It is highly recommended to validate these fields beforehand!

Arguments

  • job_name – The name of the job.
  • pdf – The PDF file to be printed and mailed.
  • name1 – First name line.
  • name2 – Second name line.
  • address1 – First address line.
  • address2 – Second address line.
  • zipcity – Line for zip / postal code and city / municipality.
  • country – Country line.

Error codes

  • 400: Bad Request – There was an error in the POST arguments.
  • 401: Unauthorized – Authentication failed.
  • 405: Method Not Allowed – send_with_address only works with POST requests.
  • 409: Conflict – Check returned message for details. Insufficient funds or problem with the argument values.

Example response

[
  {
    'status': 'CO',
    'name': 'Finvoice API - Example Company Inc',
    'created': '25.05.2009 12:14:33',
    'price': '0.84',
    'total_pages': 1,
    'recipient_count': 1,
    'is_massmail': False,
    'id': 70579
  }
]

confirm

https://postita.fi/api/confirm/{job_id}/

confirm Conrfirms job to be sended at 11 o’clock next working day. Works on only for unconfirmed jobs (Postita API status code == NE).

Arguments

  • job_id – The id of the job to be confirmed. Value ’all’ as job_id confirms all your mail.

Error codes

  • 400: Bad Request – The job is not in new / unconfirmed state.
  • 401: Unauthorized – Authentication failed.

Example response

[
  {
    "status": "CO",
    "name": "An example letter",
    "created": "14.05.2008 12:54:01",
    "price": "26.60",
    "total_pages": 270,
    "is_massmail": true,
    "recipient_count": 10
  }
]

delete

https://postita.fi/api/delete/{job_id}/

delete removes a job from the job queue. This will completely remove the job and the associated PDF file from our servers. Delete only works if the job is still waiting to be processed.

Arguments

  • job_id – The id of the job to be deleted.

Error codes

  • 400: Bad Request – The job has already been processed.
  • 401: Unauthorized – Authentication failed.

job_info

https://postita.fi/api/job_info/{job_id}/

job_info can be used to query the status of an individual job.

Arguments

  • job_id – The id of the job to be queried.

Error codes

  • 401: Unauthorized – Authentication failed.
  • 404: Not Found – The job was not found.

Example response

{
    "status": "NE",
    "name": "An example letter",
    "created": "14.05.2008 12:54:01",
    "price": "26.60",
    "total_pages": 270,
    "is_massmail": true,
    "recipient_count": 10
  }

job_list

https://postita.fi/api/job_list/{created_mindate}/
https://postita.fi/api/job_list/{status}/

job_list will return a list of all jobs associated with the querying user. It is possible to filter the returned jobs by specifying a date for the query with the optional created_mindate argument in the ’DD.MM.YYYY’ format or specifying the status of the job as argument. Returns the same data as job_info inside a list.

Arguments

  • created_mindate (optional) – Returns all jobs created after a date. The creation date filter for query in the format DD.MM.YYYY.
  • status – Retuns jobs with statuscode. Accepted values DR, NE, CO, CA, PR, SE. See Postita API status codes

Error codes

  • 400 Bad Request – Unrecognized format used in send_mindate argument.
  • 401: Unauthorized – Authentication failed.

Example response

[
    {
        "status": "SE"
        "price": "0.59"
        "total_pages": 1
        "recipient_count": 1
        "is_massmail": false
        "id": 673368
        "vat_price": "0.73"
        "name": "Testipostitus (verkkolasku)"
        "created": "19.09.2011 14:22:30"
        "pdf_splitter": ""
        "special_set": ""
        "is_einvoice": true
        "sent": "19.09.2011 14:25:27"
    },
    {
        "price": "9.79"
        "status": "DR"
        "name": "Testimassapostitus (luonnos)"
        "is_einvoice": false
        "created": "15.10.2009 12:19:07"
        "pdf_splitter": ""
        "special_set": ""
        "recipient_count": 11
        "total_pages": 11
        "id": 135140
        "vat_price": "12.04"
        "is_massmail": true
    }
]

job_pdf

https://postita.fi/api/job_pdf/{job_id}/

job_pdf can be used to fetch the original PDF file of a job.

Arguments

  • job_id – The id of the job to be fetched.

Error codes

  • 401: Unauthorized – Authentication failed.
  • 404: Not Found – The job was not found.

account_info

https://postita.fi/api/account_info/

account_info can be used to query account balance, possible credit limit and currently available funds. Currently available funds are calculated as balance minus the price of all current queued jobs.

Error codes

  • 401: Unauthorized - Authentication failed.

Example response

{
  "balance": "125.00",
  "credit_limit": "50.00",
  "available_funds": "175.00"
}

Examples

PHP

PHP with curl

<?php
$username = "username";
$password = "password";
$auth_string = $username . ":" . $password;
$pdf_file = 'letter.pdf';

$account_info_url = 'https://postita.fi/api/account_info/';
$send_url = 'https://postita.fi/api/send/';

/* First initialize curl and set some options. For more information about
   curl with PHP refer to http://php.net/manual/en/book.curl.php */
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $account_info_url);
curl_setopt($ch, CURLOPT_USERPWD, $auth_string);
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 100);

/* Getting account info */
$account_info = curl_exec($ch);
$account_info = json_decode($account_info, true);
print_r($account_info);

/* Send PDF */
/* To send a PDF, you first need to read it and encode it to base64url.
Check RFC 4648 section 5 for details. */
function base64url_encode($input) {
    return strtr(base64_encode($input), '+/', '-_');
}

$pdf = file_get_contents($pdf_file);
$pdf_b64 = base64url_encode($pdf);

/* We're creating a POST request out of the pdf and job's name. */
$data = array('job_name' => 'A letter from PHP curl API', 'pdf' => $pdf_b64);
curl_setopt($ch, CURLOPT_URL, $send_url);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); /* lighttpd fix */

/* Send the request and check for errors. */
$send_response = curl_exec($ch);
if (curl_errno($ch)) {
  echo "\n\ncURL error number: " . curl_errno($ch);
  echo "\n\ncURL error: " . curl_error($ch);
}
$send_response = json_decode($send_response, true);
print_r($send_response);

/* Clean up. */
curl_close($ch);
?>

PHP with Zend

<?php
/** Postita.fi HTTP API PHP example using Zend framework. **/
require_once 'Zend/Http/Client.php';

$account_info_url = 'https://postita.fi/api/account_info/';
$job_list_url = 'https://postita.fi/api/job_list/';
$send_url = 'https://postita.fi/api/send/';

$client = new Zend_Http_Client();
$client->setAuth('username', 'password');

$client->setUri($account_info_url);
$response = $client->request();
$account_info = json_decode($response->getBody(), true);
print_r($account_info);

$client->setUri($job_list_url);
$response = $client->request();
$job_list = json_decode($response->getBody(), true);
print_r($job_list);

function base64url_encode($input) {
    return strtr(base64_encode($input), '+/', '-_');
}

$pdf = file_get_contents('letter.pdf');
$pdf_b64 = base64url_encode($pdf);
$data = array('job_name' => 'A letter from Zend API', 'pdf' => $pdf_b64);
$data = http_build_query($data);

$client->setUri($send_url);
$client->setRawData($data)->setEncType('application/x-www-form-urlencoded');
$response = $client->request('POST');
$job_info = json_decode($response->getBody(), true);
print_r($job_info);

?>

PHP with fopen

<?php
/** Postita.fi HTTP API example
    json was included in PHP release 5.2.0. If you need to use an older
    version of PHP, you can use a PECL extension called json. Get it from
    http://pecl.php.net/package/json and install it like any other PECL
    extension (http://fi.php.net/manual/en/install.pecl.php).

    This is a raw example without any dependencies. If installing and
    using Zend framework is not a problem for you, you might want to see
    the Zend version of this example. **/

/* We put the credentials directly to the URLs. */
$account_info_url = 'https://username:password@postita.fi/api/account_info/';
$job_list_url = 'https://username:password@postita.fi/api/job_list/';
$send_url = 'https://username:password@postita.fi/api/send/';

/* Using most of the functionality exposed in the api is easy - you just need
   to send GET requests to the correct URLs and read the response. */
$fp = fopen($account_info_url, 'r', false);
$account_info = stream_get_contents($fp);
$account_info = json_decode($account_info, true);
print_r($account_info);

$fp = fopen($job_list_url, 'r', false);
$job_list = stream_get_contents($fp);
$job_list = json_decode($job_list, true);
print_r($job_list);

/* To send a PDF, you first need to read it and encode it to base64url.
   Check RFC 4648 section 5 for details. */
function base64url_encode($input) {
    return strtr(base64_encode($input), '+/', '-_');
}

$pdf = file_get_contents('letter.pdf');
$pdf_b64 = base64url_encode($pdf);

/* We're creating a POST request out of the pdf and job's name. */
$data = array('job_name' => 'A letter from PHP API', 'pdf' => $pdf_b64);
$data = http_build_query($data);
$opts = array('http' => array(
        'method' => 'POST',
        'header'=> "Content-type: application/x-www-form-urlencoded\r\n",
        'content' => $data
    )
);

/* We need to create a new stream context using the request we made. */
$send_context = stream_context_create($opts);

/* Now we can just send the request. */
$fp = fopen($send_url, 'rb', false, $send_context);
$job_info = stream_get_contents($fp);
$job_info = json_decode($job_info, true);
print_r($job_info);



/* Send finvoice example */
$send_finvoice_url = 'https://username:password@postita.fi/api/send_finvoice/';
$pdf = file_get_contents('finvoice.xml');
$pdf_b64 = base64url_encode($pdf);

/* We're creating a POST request out of the pdf and job's name. */
$data = array('job_name' => 'A Finvoice letter from PHP API', 'pdf' => $pdf_b64);
$data = http_build_query($data);
$opts = array('http' => array(
 'method' => 'POST',
 'header'=> "Content-type: application/x-www-form-urlencoded\r\n",
 'content' => $data
 )
);

/* We need to create a new stream context using the request we made. */
$send_context = stream_context_create($opts);

/* Now we can just send the request. */
$fp = fopen($send_finvoice_url, 'rb', false, $send_context);
$job_info = stream_get_contents($fp);
$job_info = json_decode($job_info, true);
print_r($job_info);


?>

Python

#!/usr/bin/env python
# coding: utf-8
# Copyright © 2008 Norfello Oy
"""Postita.fi HTTP API Python example."""

import base64
import simplejson
import urllib
import urllib2

SITE_NAME = 'postita.fi'
SITE_URL = 'https://postita.fi/'
JOB_ID = 1
ACCOUNT_INFO_URL = SITE_URL+'api/account_info/'
SEND_URL = SITE_URL+'api/send/'
JOB_INFO_URL = SITE_URL+'api/job_info/'
USERNAME = 'username'
PASSWORD = 'password'
PDF_PATH = 'letter.pdf'

# We're building an opener based on HTTP Basic Auth handler to automatically
# handle HTTP Basic Auth in our requests.
auth_handler = urllib2.HTTPBasicAuthHandler()
auth_handler.add_password(realm='Postita.fi API',
                          uri=SITE_NAME,
                          user=USERNAME,
                          passwd=PASSWORD)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)

# Most of the functionality exposed in the API is easy to use - just send GET
# requests to the correct URLs and deserialize the JSON responses.
response = urllib2.urlopen(ACCOUNT_INFO_URL)
account_info = simplejson.loads(response.read())
print account_info

response = urllib2.urlopen(JOB_INFO_URL + str(JOB_ID) + "/")
job_info = simplejson.loads(response.read())
print job_info

# To send a PDF, we need to first encode it in base64url.
# For more details about base64url, check RFC 4648 section 5.
pdf = open(PDF_PATH, 'rb')
pdf_b64 = base64.urlsafe_b64encode(pdf.read())
pdf.close()

# We need to create a POST request from our data.
data = {'pdf': pdf_b64, 'job_name': 'A letter from Python API'}
data = urllib.urlencode(data)

request = urllib2.Request(SEND_URL, data)
response = urllib2.urlopen(request)
job_info = simplejson.loads(response.read())
print job_info

Ruby

#!/usr/bin/env ruby
# Postita.fi HTTP API Ruby example
# This example requires the json gem. Get it by running 'gem install json'.

require 'net/https'
require 'rubygems'
require 'json'
require 'base64'
require 'cgi'

USERNAME = 'username'
PASSWORD = 'password'

# First some setup. Note that in this example we don't bother with SSL.
http = Net::HTTP.new('postita.fi', 443)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

# Most of the functionality exposed in the API is easy to use - just send GET
# requests to the correct URLs and deserialize the JSON responses.
http.start do |http|
  request = Net::HTTP::Get.new('/api/account_info/')
  request.basic_auth USERNAME, PASSWORD
  response = http.request(request)
  account_info = JSON(response.body)
  puts account_info
end

# To send a PDF, we need to first encode it in base64url.
# For more details about base64url, check RFC 4648 section 5.
def base64url_encode(str)
  return Base64.encode64(str).gsub!('+', '-').gsub!('/', '_').gsub!("\n", "")
end

pdf = File::open('letter.pdf', 'rb') { |f| f.read }
pdf = base64url_encode(pdf)

# Then we create a POST request out of our data and send it.
data = {
  :pdf => pdf,
  :job_name => 'A letter from Ruby'
}

http.start do |http|
  request = Net::HTTP::Post.new('/api/send/')
  request.basic_auth USERNAME, PASSWORD
  request.set_form_data(data)
  response = http.request(request)
  job_info = JSON(response.body)
  puts job_info
end

Java

/* Stringtree JSON is available from
 * https://sourceforge.net/projects/stringtree/ with documentation at
 * http://www.stringtree.org/stringtree-json.html
 *
 * The stringtree package contains much more than JSON tools, but the
 * JSON part is small and downloadable in a separate jar. Stringtree is
 * licensed under both GNU LGPL and Apache Software License.
 *
 * At least version 2.0.9 should be used. Other, less simple JSON tools are
 * listed at http://www.json.org/
 *
 * A public domain Base64 encoder is available at
 * http://iharder.sourceforge.net/current/java/base64/
 */

import org.stringtree.json.JSONReader;
import org.stringtree.json.JSONValidatingReader;
import org.stringtree.json.ExceptionErrorListener;
import java.net.*;
import java.io.*;
import java.util.*;

class AccountInfo {
    public AccountInfo(Object json) {
        Map info = (Map)json;
        // do not assume anything extra of the input so that the code
        // doesn't break with possible future extensions
        balance = (String)info.get("balance");
        available_funds = (String)info.get("available_funds");
        reference = (String)info.get("reference");
        // credit_limit may be null
        credit_limit = (String)info.get("credit_limit");
    }

    public String toString() {
        return "balance: "+balance+"\navailable_funds: "+available_funds+
            "\nreference: "+reference+
            "\ncredit_limit: "+credit_limit;
    }

    private String balance, available_funds, reference, credit_limit;
}

class JobInfo {
    public JobInfo(Object json) {
        Map info = (Map)json;
        name = (String)info.get("name");
        created = (String)info.get("created");
        // sent may be null
        sent = (String)info.get("sent");
        total_pages = (Long)info.get("total_pages");
        recipient_count = (Long)info.get("recipient_count");
        price = (String)info.get("price");
        is_massmail = (Boolean)info.get("is_massmail");
        status = (String)info.get("status");
    }

    public String toString() {
        return "name: "+name+"\ncreated: "+created+"\nsent: "+sent+
            "\ntotal_pages: "+total_pages+"\nrecipient_count: "+recipient_count+
            "\nprice: "+price+"\nis_massmail: "+is_massmail+
            "\nstatus: "+status;
    }

    private String name, created, sent, price, status;
    private long total_pages, recipient_count;
    private boolean is_massmail;
}

class PostitaAPI {
    public PostitaAPI(final String username, final String password) {
        Authenticator.setDefault(new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(username,
                                                      password.toCharArray());
                }
            });
    }

    public AccountInfo getAccountInfo() {
        String json = httpGet(SITE_URL+"api/account_info/");
        return new AccountInfo(jsonReader.read(json));
    }

    public JobInfo getJobInfo(long job_id) {
        String json = httpGet(SITE_URL+"api/job_info/"+job_id+"/");
        return new JobInfo(jsonReader.read(json));
    }

    public void send(String pdf_filename, String job_name) {
        try {
            FileInputStream in = new FileInputStream(new File(pdf_filename));
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            OutputStream out = new Base64.OutputStream
                (bytes, Base64.ENCODE | Base64.URL_SAFE);
            byte[] chunk = new byte[4096];
            for (;;) {
                int count = in.read(chunk);
                if (count == -1) break;
                out.write(chunk, 0, count);
            }
            Map<String, String> map = new HashMap<String, String>();
            map.put("pdf", bytes.toString("ASCII"));
            map.put("job_name", job_name);
            httpPost(SITE_URL+"api/send/", map);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String readInputStream(InputStream stream) throws IOException {
        Reader in = new InputStreamReader(stream);
        StringBuilder data = new StringBuilder();
        char[] chunk = new char[4096];
        for (;;) {
            int count = in.read(chunk);
            if (count == -1) break;
            data.append(chunk);
        }
        return data.toString();
    }

    private String httpGet(String urlString) {
        try {
            return readInputStream(new URL(urlString).openStream());
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void httpPost(String urlString, Map<String, String> data) {
        HttpURLConnection c = null;
        int code = -1;
        try {
            StringBuilder request = new StringBuilder();
            boolean first = true;
            for (String key : data.keySet()) {
                String value = data.get(key);
                if (!first) request.append("&");
                request.append(URLEncoder.encode(key, "UTF8"));
                request.append("=");
                request.append(URLEncoder.encode(value, "UTF8"));
                first = false;
            }
            c = (HttpURLConnection)new URL(urlString).openConnection();
            c.setDoOutput(true);
            OutputStreamWriter w = new OutputStreamWriter(c.getOutputStream());
            w.write(request.toString());
            w.flush();
            w.close();
            readInputStream(c.getInputStream());
            code = c.getResponseCode();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
        if (code != 200)
            throw new RuntimeException("got response code: "+code);
    }

    private JSONReader jsonReader =
        new JSONValidatingReader(new ExceptionErrorListener());
    //private static final String SITE_URL = "http://localhost:8000/";
    private static final String SITE_URL = "https://postita.fi/";
}

public class ApiTest {
  public static void main(String[] args) {
      PostitaAPI api = new PostitaAPI("username", "password");
      System.out.println("Account info:\n");
      try {
          AccountInfo accountInfo = api.getAccountInfo();
          System.out.println(accountInfo.toString());
      } catch(Exception e) {
          e.printStackTrace();
      }
      System.out.println("\nJob info:\n");
      try {
          JobInfo jobInfo = api.getJobInfo(1035);
          System.out.println(jobInfo.toString());
      } catch(Exception e) {
          e.printStackTrace();
      }
      System.out.println("\nSend:\n");
      try {
          api.send("letter.pdf", "A letter from Java API");
          System.out.println("OK\n");
      } catch(Exception e) {
          e.printStackTrace();
      }
  }
}

References

1http://www.ietf.org/rfc/rfc2616.txt

2http://www.finvoice.info/

3http://www.ietf.org/rfc/rfc2617.txt

4http://www.ietf.org/rfc/rfc4648.txt