Diff for "API/Hacking"

Not logged in - Log In / Register

Differences between revisions 1 and 30 (spanning 29 versions)
Revision 1 as of 2008-07-31 17:21:33
Size: 30190
Editor: cpe-24-193-113-134
Comment:
Revision 30 as of 2024-07-15 14:52:38
Size: 34922
Editor: jugmac00
Comment: remove outdated comment
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
||<tablestyle="float:right; font-size: 0.9em; width:40%; background:#F1F1ED; margin: 0 0 1em 1em;" style="padding:0.5em;"><<TableOfContents>>||
Line 32: Line 34:
the responses you'll get back. I'll be skipping over the fact that the responses you'll get back.

I'll be skipping over the fact that to make authenticated requests,
Line 34: Line 38:
credentials, or they'll fail with response code 401
("Unauthorized"). To see how to sign a request, see "Signing Requests"
below.


== An entry: your user account ==

Let's consider one of the objects exposed by the web service: your own
user account. The URL to your user account in Launchpad's web service
is "http://api.launchpad.net/beta/~{your-user-name}": for instance,
"http://api.launchpad.net/beta/~salgado". If you send a GET request to
"http://api.launchpad.net/beta/+me" you'll be redirected to your user
account.

(How are you supposed to know the URL to your user account without
reading this document? That's a good question which we'll deal with
later. But for now, note that the URL to your user account in the
Launchpad web application looks like
"http://www.launchpad.net/~salgado", and that
"http://www.launchpad.net/+me" will redirect you to your user account
on the website.)


=== GET ===

To find out about your user account you send a HTTP GET request to
that URL:

{{{
   GET /people/~your-user-name HTTP/1.1
   Host: api.launchpad.net
}}}

(Again, to actually make this request, you'll need to get an OAuth
credential and digitally sign the request, as described below in
"Request Signing.")

You'll get back a response document containing a JSON hash:

{{{
   200 OK
   Content-type: application/json

   {"languages_collection_link": "http:\/\/api.launchpad.net\/beta\/~your-user-name\/languages", "members_collection_link": "http:\/\/api.launchpad.net\/beta\/~your-user-name\/members", ... }
}}}

That looks ugly as a string. It looks better when you deserialize it
from JSON into a native-language data structure. Here's what I get
when I turn it into a Python dictionary.

{{{
{u'admins_collection_link': u'http://api.launchpad.net/beta/~your-user-name/admins',
 u'confirmed_email_addresses_collection_link': u'http://api.launchpad.net/beta/~your-user-name/confirmed_email_addresses',
credentials.

Unauthenticated requests can see all public objects, which are the majority,
but they can't see private objects (such as hidden email addresses,
security-sensitive bugs, or private teams), they can't change anything, and
of course they have no concept of a "me" resource to start from. Trying to access a
private resource will give you a "401 Unauthenticated" error.

To see how to sign a request, see [[../SigningRequests| "Signing Requests"]].

= The homepage =

Launchpad's website has a homepage that acts as a jumping-off point to
projects, bugs, people, answers, and so on. The web service has a
homepage, too. It's a lot more sparse than the web site's homepage but
it also makes a good jumping-off point.

The root of the web service is http://api.launchpad.net/1.0/. I'll
show you the staging server for these examples so you don't
accidentally change something you don't mean to change. To get the
service's homepage, just sent an HTTP GET request to /1.0.

{{{
    GET /1.0 HTTP/1.1
    Host: api.staging.launchpad.net
}}}

The response you get back will look something like this:

{{{
    200 OK
    Content-type: application/json

    {"people_collection_link": "http:\/\/api.staging.launchpad.net\/1.0\/people", "bugs_collection_link": "http:\/\/api.staging.launchpad.net\/1.0\/bugs", "me_link": "http:\/\/api.staging.launchpad.net\/people/+me"}
}}}

That's a [[http://json.org/|JSON]] document, which you can turn into a
native-language data structure using whatever libraries are available
for the programming language you're using. (In launchpadlib, we use
the Python simplejson library to process JSON documents.) Process it
and you'll have something like this Python data structure:

{{{
    { u"people_collection_link":
        u"http://api.staging.launchpad.net/1.0/people",
      u"bugs_collection_link":
        "http://api.staging.launchpad.net/1.0/bugs",
      u"me_link":
        "http://api.staging.launchpad.net/people/+me"}
}}}

('''Note:''' If you're following along and you sent a GET to /1.0/,
you'll see that the "me_link" isn't actually present yet. The URL
works, it's just that the web service homepage doesn't link to it
yet. We're going to add that link soon, and it's very useful for
purposes of discussion, so for now just pretend it's there.)

Almost all of the documents served through the Launchpad web service
are JSON documents. In fact, almost all of them describe dictionaries
of key-value pairs, like this one.

The Launchpad website's homepage contains links to other parts
of Launchpad. This homepage serves the same purpose: it tells you
about the major parts of the Launchpad web service. Once you've got
the hang of the web service you can bypass the web service's homepage,
just as you might bypass the Launchpad homepage, but it's a good place
to start.

This document contains three links, and by following the links you can
explore the web service. As we expose more of Launchpad through the
web service, you'll see more links added to this document.

 * people_collection_link: This is the list of people tracked by Launchpad.
 * bugs_collection_link: This is the list of bugs tracked by Launchpad.
 * me_link: This is a link to your user account.

By convention, all fields that are links to other parts of the
Launchpad web service have names ending in '_link'. Other fields might
have URLs as values, and you can follow those URLs if you want, but
they probably don't have anything to do with the Launchpad web
service.

Apart from the homepage, which you've just seen, there are two basic
types of objects in launchpad: ''entries'' and ''collections''. An
entry is a single object, like a bug or your user account; a
collection is a group of entries. Links to collections always have
names that end in "_collection_link"; links to entries always have
names that just end in "_link".


= An entry: your user account =

If you follow the "me_link" by making a GET request to
http://api.staging.launchpad.net/people/+me...

{{{
    GET /1.0/people/+me HTTP/1.1
    Host: api.staging.launchpad.net
}}}

...you'll be redirected to the Launchpad web service's view of your
user account.

{{{
    302 Found
    Location: http://api.staging.launchpad.net/~your-user-name
}}}

Note the symmetry with the Launchpad website. If you visit
http://www.launchpad.net/people/+me in your web browser, you'll be
redirected to http://www.launchpad.net/~your-user-name.

Your user account is an entry-type resource. It responds to a specific
HTTP interface that's common to all entry resources exposed by the
Launchpad web service: GET, PUT, and PATCH. Entry resources may also
respond to custom named GET and POST operations which are different
for every kind of entry; we'll cover those later.


== Reading resources: GET ==

The most basic operation on an entry resource is GET. To find out
about your user account you send a HTTP GET request to that URL:

{{{
   GET /1.0/~your-user-name HTTP/1.1
   Host: api.staging.launchpad.net
}}}

You'll get back a response document containing a JSON hash, just like
you did when you sent GET to the service root. Here's what the hash
looks like when I convert it to a Python dictionary.

{{{
{u'admins_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/admins',
 u'confirmed_email_addresses_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/confirmed_email_addresses',
Line 88: Line 175:
 u'deactivated_members_collection_link': u'http://api.launchpad.net/beta/~your-user-name/deactivated_members',  u'deactivated_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/deactivated_members',
Line 90: Line 177:
 u'expired_members_collection_link': u'http://api.launchpad.net/beta/~your-user-name/expired_members',  u'expired_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/expired_members',
Line 93: Line 180:
 u'indirect_participations_collection_link': u'http://api.launchpad.net/beta/~your-user-name/indirect_participations',
 u'invited_members_collection_link': u'http://api.launchpad.net/beta/~your-user-name/invited_members',
 u'irc_nicknames_collection_link': u'http://api.launchpad.net/beta/~your-user-name/irc_nicknames',
 u'indirect_participations_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/indirect_participations',
 u'invited_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/invited_members',
 u'irc_nicknames_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/irc_nicknames',
Line 98: Line 185:
 u'jabber_ids_collection_link': u'http://api.launchpad.net/beta/~your-user-name/jabber_ids',  u'jabber_ids_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/jabber_ids',
Line 100: Line 187:
 u'languages_collection_link': u'http://api.launchpad.net/beta/~your-user-name/languages',  u'languages_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/languages',
Line 104: Line 191:
 u'members_collection_link': u'http://api.launchpad.net/beta/~your-user-name/members',
 u'members_details_collection_link': u'http://api.launchpad.net/beta/~your-user-name/members_details',
 u'memberships_details_collection_link': u'http://api.launchpad.net/beta/~your-user-name/memberships_details',
 u'mugshot_link': u'http://api.launchpad.net/beta/~your-user-name/mugshot',
 u'members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/members',
 u'members_details_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/members_details',
 u'memberships_details_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/memberships_details',
 u'mugshot_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/mugshot',
Line 109: Line 196:
 u'open_membership_invitations_collection_link': u'http://api.launchpad.net/beta/~your-user-name/open_membership_invitations',
 u'participants_collection_link': u'http://api.launchpad.net/beta/~your-user-name/participants',
 u'participations_collection_link': u'http://api.launchpad.net/beta/~your-user-name/participations',
 u'preferred_email_address_link': u'http://api.launchpad.net/~your-username/+email/your.address@foo.com',
 u'proposed_members_collection_link': u'http://api.launchpad.net/beta/~your-user-name/proposed_members',
 u'resource_type_link': u'http://api.launchpad.net/beta/#person',
 u'self_link': u'http://api.launchpad.net/beta/~your-user-name',
 u'sub_teams_collection_link': u'http://api.launchpad.net/beta/~your-user-name/sub_teams',
 u'super_teams_collection_link': u'http://api.launchpad.net/beta/~your-user-name/super_teams',
 u'open_membership_invitations_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/open_membership_invitations',
 u'participants_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/participants',
 u'participations_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/participations',
 u'preferred_email_address_link': u'http://api.staging.launchpad.net/~your-username/+email/your.address@foo.com',
 u'proposed_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/proposed_members',
 u'resource_type_link': u'http://api.staging.launchpad.net/1.0/#person',
 u'self_link': u'http://api.staging.launchpad.net/1.0/~your-user-name',
 u'sub_teams_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/sub_teams',
 u'super_teams_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/super_teams',
Line 121: Line 208:
 u'wiki_names_collection_link': u'http://api.launchpad.net/beta/~your-user-name/wiki_names'}
}}}

That's a lot of information. You can consult the reference
documentation (XXX)
for more information on what each of the fields of
this dictionar
y mean. What's important is that there are three and
only
three kinds of fields:
 u'wiki_names_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/wiki_names'}
}}}


That's a lot of information. You can consult [[https://launchpad.net/+apidoc|the reference documentation]]
for more information on what each of the fields of this hash
mean. What's important is that there are three and onl
y three kinds of
fields:
Line 131: Line 219:
 2. Links to other resources. 'mugshot_link' points to your mugshot image. 'preferred_email_address_link' points to a resource that represents your preferred email address. Remember, every object in Launchpad has its own URL, even tiny objects like email addresses and languages. By Launchpad convention, all links between resources have field names that end in '_link'. Two of these links are especially important, and you'll find them present in every document of this sort.  2. Links to other entry-type resources. These work the same way as the "me_link" in the JSON representation of the Launchpad server root. 'mugshot_link' points to your mugshot image. 'preferred_email_address_link' points to a resource that represents your preferred email address. Remember, every object in Launchpad has its own URL, even tiny objects like email addresses and languages. Again, by Launchpad convention, all links between resources have field names that end in '_link'. Two of these links are especially important, and you'll find them present in every representation of an entry-type resource.
Line 135: Line 223:
  * 'resource_type_link' is a link to a machine-readable description of this resource. This is how you can know which fields can be modified and which can't. In the larger sense, these descriptions are also how you can discover that the URL to your Launchpad user account is "http://api.launchpad.net/~your-user-name". For more on this see "WADL Description" below.

 3. Links to collections of resources. A person in Launchpad can be associated with more than one email address, but only one of those can be the 'preferred' address at any one time. By Launchpad convention, all links to collections have field names that end in '_collection_link'. The 'preferred_email_address_link' field points to whatever address is currently preferred. The 'confirmed_email_addresses_collection_link' field points to a list containing all the addresses. For more on collections, see "The list of bugs" below.


=== Modifying resources ===
  * 'resource_type_link' is a link to a machine-readable description of this resource. You can use this to do introspection on the resource, finding out what special operations are available, or which of the fields in the representation can be modified. For more on this see "WADL Description" below.

 3. Links to collection-type resources. A person in Launchpad can be associated with more than one email address, but only one of those can be the 'preferred' address at any one time. The 'preferred_email_address_link' field points to whatever address is currently preferred. The 'confirmed_email_addresses_collection_link' field points to a list containing all the addresses. For more on collections, see "The list of bugs" below.


== Modifying resources: PUT ==

(To make any changes, you must send a signed request.)
Line 176: Line 266:
    PUT /people/~{your-user-name} HTTP/1.1
    Host: api.launchpad.net
    PUT /1.0/~your-user-name HTTP/1.1
    Host: api.staging.launchpad.net
Line 183: Line 273:
The response should indicate that my changes were made: The response should indicate that the changes were made:
Line 189: Line 279:
=== PATCH ===
== Modifying resources: PATCH ==
Line 204: Line 295:
    PATCH /people/~{your-user-name} HTTP/1.1
    Host: api.launchpad.net
    PATCH /people/~your-user-name HTTP/1.1
    Host: api.staging.launchpad.net
Line 217: Line 308:
Note that PATCH is not yet an official HTTP method. It's defined in [[http://tools.ietf.org/html/draft-dusseault-http-patch-11| an Internet-Draft]. Because
it's not standard you might not be able to use it from your web
client. In particular, you can't use PATCH if you're writing an Ajax
application.


=== Changing links ===

== Changing links ==
Line 230: Line 316:
    # http://api.launchpad.net/~your-username/+email/your.address@foo.com
}}}

Since the current value is expressed as a URL, you change the value by
    # http://api.staging.launchpad.net/~your-username/+email/your.address@foo.com
}}}

Since the value is shown as a URL, you change the value by
Line 240: Line 326:
    Host: api.launchpad.net     Host: api.staging.launchpad.net
Line 244: Line 330:
     "http://api.launchpad.net/~your-username/+email/another.address@bar.com"}      "http://api.staging.launchpad.net/~your-username/+email/another.address@bar.com"}
Line 251: Line 337:
its own permanent URL, accessible as its 'self_link' field. Stick one
of those
URLs in the document describing your user account, and you
its own permanent URL, accessible as its 'self_link' field. Look through the collection, find the address you want, and stick its 'self_link' URL
into
the document describing your user account. Then you
Line 254: Line 340:
'preferred' one. In Python code the URL change might look like this: 'preferred' one. In Python code the link change might look like this:
Line 259: Line 345:

Then you'd make a PUT or PATCH request to send the change to the server.
Line 266: Line 354:
"http://api.launchpad.net/beta/~{your-user-name}/confirmed_email_addresses".


=== Error handling ===
"http://api.staging.launchpad.net/1.0/~{your-user-name}/confirmed_email_addresses".


== Error handling ==
Line 280: Line 368:
    Host: api.launchpad.net     Host: api.staging.launchpad.net
Line 285: Line 373:
You'll get this response: ...you'll get this response:
Line 295: Line 383:
== A collection: the list of bugs ==

The user account resource is a typical example of what we call an
"
entry resource": one that represents one specific thing. The other
main sort of resource is what we call a "collection resource": one
that acts as a container for a number of other resources.
= A collection: the list of bugs =

The user account resource is a typical example of an entry-type
resource
: one that represents one specific thing. The other main sort
of resource in the Launchpad
web service is a collection-type
resource
: one that acts as a container for a number of other
resources.
Line 307: Line 396:
One interesting collection is the list of filed bugs. Its address is
"http://api.launchpad.net/beta/bugs". Send a GET request to that URL
and you'll get back a JSON document that looks like this:
One interesting collection is the collection of filed bugs. Remember
the homepage of the Launchpad web service? The 'bugs_collection_link' there is the URL to the collection
of bugs.

Send a GET request to that URL...

{{{
    GET /1.0/bugs HTTP/1.1
    Host: api.staging.launchpad.net
}}}

...and you'll get back a JSON document that looks like this:
Line 315: Line 413:
        u'http://api.launchpad.net/beta/bugs?ws.start=75&ws.size=75',
      u'resource_type_link' : u'http://api.launchpad.dev/beta/#bugs',
        u'http://api.staging.launchpad.net/1.0/bugs?ws.start=75&ws.size=75',
      u'resource_type_link' : u'http://api.launchpad.dev/1.0/#bugs',
Line 342: Line 440:
GET request to http://api.launchpad.net/beta/bugs?ws.start=9&ws.size=3 GET request to http://api.staging.launchpad.net/1.0/bugs?ws.start=9&ws.size=3
Line 345: Line 443:
http://api.launchpad.net/beta/bugs and retrieved the first 75. http://api.staging.launchpad.net/1.0/bugs and retrieved the first 75.
Line 357: Line 455:
specific resources--we call these "named operations" because they're
identified by name rather than by one of the standard HTTP methods.
specific resources. We call these "named operations" because they're
identified by name rather than by the name of one of the standard HTTP methods.
Line 370: Line 468:
=== Read operations (GET) === == Read operations (GET) ==
Line 374: Line 472:
http://api.launchpad.net/beta/people, but for most applications you http://api.staging.launchpad.net/1.0/people, but for most applications you
Line 382: Line 480:
    http://api.launchpad.net/beta/people?ws.op=find&text={text}     http://api.staging.launchpad.net/1.0/people?ws.op=find&text={text}
Line 389: Line 487:
http://api.launchpad.net/beta/people. There's no secret here.) http://api.staging.launchpad.net/1.0/people. There's no secret here.)
Line 395: Line 493:
http://api.launchpad.net/beta/people?ws.op=find&text=foo gives you the
same kind of document as getting http://api.launchpad.net/beta/people,
http://api.staging.launchpad.net/1.0/people?ws.op=find&text=foo gives you the
same kind of document as getting http://api.staging.launchpad.net/1.0/people,
Line 408: Line 506:
=== Write operations (POST) === == Write operations (POST) ==
Line 416: Line 514:
    POST /beta/people HTTP/1.1
    Host: api.launchpad.net
    POST /1.0/people HTTP/1.1
    Host: api.staging.launchpad.net
Line 436: Line 534:
    Location: http://api.launchpad.net/beta/~{name}     Location: http://api.staging.launchpad.net/1.0/~{name}
Line 440: Line 538:
find the new team at http://api.launchpad.net/beta/~{name}. Now you find the new team at http://api.staging.launchpad.net/1.0/~{name}. Now you
Line 447: Line 545:
= Request Signing =

If you send the GET requests given in this document as is to
api.launchpad.net, you'll get a response code of 401
("Unauthorized"). Launchpad's web service only responds to requests
that have been digitally signed with a particular Launchpad user's
authorization key.

This doesn't have to be *your* key. You can have a simple script that
uses your own Launchpad authorization key, but you can also run a
website that gathers its users' authorization keys and makes requests
to the web service on their behalf. This is safe because you
authorization keys have nothing to do with your Launchpad
password. They're a way of delegating a limited set of privileges to
some other program. If a program proves untrustworthy, you only have
to revoke its key.

The standard HTTP authentication mechanisms (Basic and Digest) aren't
sophisticated enough to handle these cases. That's why Launchpad has
adopted the OAuth standard (http://oauth.net) for authentication. It's
a little more work to set up than just sending your Launchpad username
and password to the web service, but it's more versatile and more
secure.


=== Getting credentials ===

A user can set up an authorization token from within Launchpad, by
visiting http://www.launchpad.net/~{name}/+oauth-tokens. It's
reasonable to ask your users to set up a token before they use your
program, and provide their Launchpad credentials in a config file or
as command-line arguments to your script. But you can also create a
new set of credentials from within your application.

The basic workflow is always the same, but the details are different
if you're writing a standalone application, versus creating a website.

0. Pick a consumer key

The consumer key identifies your application and it should be
hard-coded somewhere in your code. Everyone who uses your application
will send the same consumer key.

We recommend you choose the name of your program as the consumer
key. Don't append the version number unless you want your users to get
new application keys for every new version. For this example I'll use
the consumer key 'myconsumerkey'.

 1. Get a request token

Getting your program's user to create a new credential for the program is a multi-step process. The request token is a unique string that Launchpad uses to keep track of your program between steps.

To obtain a request token, send a POST request to
http://www.launchpad.net/+request-token. (Note: *not*
api.launchpad.net!) This is the same kind of POST a web browser does
when it submits a form. You should submit the following values in
form-encoded format.

 * oauth_consumer_key: Your consumer key
 * signature_method: The string "PLAINTEXT"
 * oauth_signature: The string "&".

So the HTTP request might look like this:

{{{
    POST /+request-token HTTP/1.1
    Host: www.launchpad.net
    Content-type: application/x-www-form-urlencoded

    oauth_consumer_key=myconsumerkey&signature_method=PLAINTEXT&oauth_signature=%26
}}}

The response should look like this:
= A hosted file: a user's mugshot =

The fourth type of resource is the hosted file. This resource is a
front-end to a file stored in Launchpad's file library. The example
I'll use is a person's mugshot image. You can find the URL to this
resource by looking under 'mugshot_link' in the JSON representation of
a person. It should look like "/1.0/~{person}/mugshot".


== Read (GET) ==

When you send a GET request to a hosted file resource, you'll get back
an HTTP redirect to a file in Launchpad's file library.

{{{
    GET /1.0/~salgado/mugshot HTTP/1.1
    Host: api.staging.launchpad.net
}}}

You'll get back a response that looks like this:

{{{
    303 See Other
    Location: Location: http://staging.launchpad.net:58000/92/mugshot
}}}

Send a second GET request to the URL in Location, and you'll get the
image document itself.

{{{
    GET /92/mugshot HTTP/1.1
    Host: staging.launchpad.net:58000
}}}
Line 523: Line 581:

    oauth_token_secret=&oauth_token={request token}
}}}

oauth_token_secret will be empty; we don't use it. oauth_token will be
a random-looking string of letters and digits. Save this for step 3.

 2. Authenticate the user.

Now the user needs to 1) log in to Launchpad, and 2) tell Launchpad
how much authority they're delegating to your program. You need to get
them to visit the following URL in their web browser:

{{{
    http://www.launchpad.net/+authorize-token?oauth_token={request token}
}}}

 2a. Building a website?

If your program is a website that your users visit, you can send them
an HTTP redirect. Be sure to also specify the 'oauth_callback' field
as a URL on your website.

{{{
    http://www.launchpad.net/+authorize-token?oauth_token={request token}&oauth_callback={URL to within your website}
}}}

Once the user delegates some of their privileges to your website
Launchpad will redirect the user back to that URL, so that they can
resume using your site.

 2b. Building a client-side program?

If your program runs on the clients' computer rather than through
their web browser, you don't have to worry about redirecting back to
your web page. But you do have to worry about opening the Launchpad
page in their web browser in the first place. Take a look at the
open_url_in_browser() function defined in launchpadlib's
launchpad.py; it works well on most Linux systems. Just open up their
web browser to the +authorize-token page.

If your program isn't running in the web browser, how are you supposed
to know when the user is done with the +authorize-token page? There's
no 'oauth_callback' equivalent that Launchpad can use to send a signal
to your client-side program. What you need to do is have the
_end-user_ tell you when they're done with +authorize-token.

The launchpadlib library prints an explanatory message after it opens
+authorize-token in your web browser. It waits for the end-user to
authorize access through their web browser, and then switch back to
the launchpadlib window and hit return. If you're writing a GUI
program, you can have the end-user click a button once they're done
authorizing your program to talk to Launchpad on their behalf.

For an example of good interface design around these constrains, look
at F-Spot's Flickr integration. The first time you export a photo to
Flickr you need to click an "Authorize" button. This opens up a web
browser to a page on Flickr. You log in to Flickr and authorize F-Spot to
access the Flickr web service on your behalf. Then you go back to
F-Spot and click a "Complete Authorization" button. After that point,
F-Spot can talk to Flickr with your credentials.

(Flickr doesn't use OAuth, but its system has the same architecture as
OAuth, so the user interface can work the same way.)

 3. Exchange the request token for an access token

You can't do much with the request token. It's only good for
coordinating with Launchpad while the user decides whether or not to
let you make web service requests in their name. Once the user has
delegated some of their authority to you, you need to exchange the
request token for a new token that has their permissions associated
with it.

If you're writing a website, you'll know this happens when Launchpad
redirects your user back to the URL you specified as
'oauth_callback'. If you're writing a client-side program, you'll know
when your user clicks the "Complete Authorization" button or hits
enter or whatever it was you told them to do when they were done on
the Launchpad side.

Now you make a GET request to
http://www.launchpad.net/+access-token. Provide the following data in
the query string:

  * oauth_token: Your request token from step 1
  * oauth_signature: The string '&'

So the URL you GET should look like this:

{{{
    http://www.launchpad.net/+access-token?oauth_token={request token}&oauth_signature=%26
}}}

Basically, you're looking up a record using the request token as a
key. The record was created when the end-user told Launchpad it was
okay to delegate their authorization to you.

You should get a response that looks like this:

{{{
    200 OK
    oauth_token={access token}&oauth_token_secret={access token secret}
}}}

Put those two pieces of information in some persistent storage. You'll
need them as part of every request you make to Launchpad's web
service.


=== Using credentials ===

Now that you've got an access token and a secret for a particular
Launchpad user, you won't have to go through that again for that
user. But there's still the matter of digitally signing your requests
with that token.

Unlike the process of getting credentials, which is pretty specific to
Launchpad, the process of digitally signing a request is completely
mechanical. The mechanics are covered in detail in the OAuth standard
(http://oauth.net/core/1.0/), and there are also OAuth libraries in
most popular programming languages that can sign an HTTP request given
an access token and secret. So I'm not going to go into much detail on
how to sign a request. It's a general problem and there are plenty of
places to go if you need help, and lots of sample code to look at.


= WADL Description =
    Content-Type: image/jpeg

    [image goes here]
}}}


== Write (PUT) ==

To modify a hosted file resource, send a PUT request to its URL. (This
is the "/1.0/~{person}" URL on api.*.launchpad.net, not the library
URL you get back as a redirect.) Make sure to set the Content-Type
header to the MIME type of the file you're writing. You can also set the Content-Disposition header to specify the server-side filename of the file. Here's how to
change a person's mugshot.

{{{
    PUT /1.0/~salgado/mugshot HTTP/1.1
    Host: api.staging.launchpad.net
    Content-Type: image/png
    Content-Disposition: attachment; filename=my-mugshot.png

    [image goes here]
}}}


=== Error handling ===

Launchpad may enforce restrictions on the files you write. For
instance, a mugshot must be an image file, and the image must have a
specific height and width. If you send a bad file to Launchpad, you'll
get a response that looks something like this.

{{{
    400 Bad Request

    The file uploaded was not recognized as an image; please
    check it and retry.
}}}


== Delete (DELETE) ==

To delete a hosted file, send a DELETE request to its URL:

{{{
    DELETE /1.0/~salgado/mugshot HTTP/1.1
    Host: api.staging.launchpad.net
}}}

This will not necessarily delete the file from the Launchpad library,
because there might be other references to it within Launchpad. But
the file will be disassociated from the hosted-file resource. In this
case, the "salgado" user will stop having a mugshot, and any future
attempts to GET /1.0/~salgado/mugshot will return HTTP 404 ("Not
Found").


= Using the reference documentation =
Line 662: Line 649:
Most web service providers put this sort of information in a big prose
document that you're supposed to read when you write a client. We do
have that big document (the reference documentation), but we also have
a machine-readable document that describes the quirks of this
particular web service: a [[https://wadl.dev.java.net/| WADL]] document.

In fact, the reference documentation is just a human-readable
If you don't know the capabilities of a resource, you can look it up
in [[https://launchpad.net/+apidoc|the reference documentation]].
First, look at the resource's 'resource_type_link'. It'll be something
like "http://api.launchpad.dev/1.0/#bugs". Take the anchor part of
that URL (here, "#bugs"), and use it as an anchor into the reference
documentation.

That is, if the 'resource_type_link' is
"http://api.launchpad.dev/1.0/#bugs", you can find human-readable
documentation about that resource by going to
https://launchpad.net/+apidoc/devel.html#bugs in your
web browser.

The reference documentation will tell you about all the fields in an object's JSON
representation, and all the HTTP methods it will respond to.

= Two ways to save time and bandwidth =

== Ask for compressed documents ==

Launchpad's web service serves XML and JSON documents that compress
very well. You'll get the documents faster and save bandwidth if you ask
Launchpad to compress documents before sending them over the network.

You do this by specifying a compression algorithm in the "TE" request
header. Launchpad's web service supports two compression algorithms:
"gzip", the standard gzip algorithm handled by
[[http://www.python.org/doc/lib/module-gzip.html| Python's gzip module]], and "deflate", the algorithm handled by
[[http://www.python.org/doc/lib/module-zlib.html| Python's zlib module]]. Both of these are as defined in
[[http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5| the HTTP standard.]]

So your TE header will look like this:

{{{
  TE: deflate
}}}
or like this:

{{{
  TE: gzip
}}}

Launchpad will send you compressed data, and will set the
Transfer-Encoding response header to the name of the compression
algorithm it used. It'll either look like this:

{{{
   Transfer-Encoding: deflate
}}}

or like this:

{{{
   Transfer-Encoding: gzip
}}}

Most web servers use the Accept-Encoding and Content-Encoding headers
to handle compression. This isn't technically wrong, but it interferes
with other optimizations we want to make, so we do things differently.


== Cache the documents you get ==

It's important that you cache the documents you get from Launchpad,
especially documents like the WADL file that are large and don't
change very often.

Here's how [[http://code.google.com/p/httplib2/| httplib2]] does caching
(launchpadlib is based on httplib2). When it makes a GET request and
gets a document back, it stores the document in a file, along with all
of the HTTP response headers. The next time it needs to make that GET
request, it uses the cached response instead of making another request
to get the same data back.

But how do you know that the document hasn't secretly changed since
the last time you retrieved it? Here's how to check without making the
same request again. When you get a document from Launchpad, you'll
also get a value for the HTTP response header "ETag". It'll look like
this:

{{{
    ETag: "924eb0c15c911d64e633b5f012d046d04a83b571"
}}}

If you suspect the document has changed, make a GET request to the
document's URL, but include the ETag in the HTTP header
"If-None-Match".

{{{
    If-None-Match: "924eb0c15c911d64e633b5f012d046d04a83b571"
}}}

If the document has changed, this will work just like a normal GET
request. You'll get back the changed version of the document,
including a new ETag. But if the document is the same as it used to
be, you'll get an HTTP response that looks like this:

{{{
    304 Not Modified
}}}

Instead of sending the document again, Launchpad is telling you that
you already have the most recent version. This is called "conditional
GET", and if you need more detail there's a lot more information about
it on the web.

Launchpad doesn't serve ETags for collections, only for individual
objects and for the server root.


= Avoid stepping on other peoples' toes =

Let's say I'm using the Launchpad website to change the details of a
bug, and you're using Launchpad's web service. I change the details of
a bug. A few seconds later, you make a contradictory change to the
same bug. You've overwritten my change without even knowing about it.

Here's how to avoid that problem. Remember the ETags from the previous
section? Whenever you make a PUT or PATCH request to an object,
include that object's ETag in the "If-Match" HTTP header.

{{{
    If-Match: "924eb0c15c911d64e633b5f012d046d04a83b571"
}}}

If no one has made a change to this object, your PUT or PATCH will go
through. If someone else made a change to the object that you haven't
seen, you'll get an HTTP response that looks like this:

{{{
    412 Precondition Failed
}}}

Launchpad is telling you that it didn't make your change because another change happened that you don't know about. You'll have a chance to GET the new
version of the object (complete with a new ETag), work out any
contradictions between the other person's change and the change you
want to make, and re-submit with the new ETag.


= WADL Description =

Like most web service providers we publish [[http://launchpad.net/+apidoc/| a prose document]] describing
the capabilities of all our resources. But we also publish a
machine-readable document containing the same information. It's
written in [[https://wadl.dev.java.net/| WADL]] format, and you can
use it as a basis for tools that interact with the web service. In
fact, the reference documentation is just a human-readable
Line 670: Line 797:
is a thin wrapper on top of a WADL library. Almost every interesting
aspect of the web service is described in this file. You can use it as
a basis for your own tools that talk to Launchpad. It's analogous to
the HTML forms you use to manipulate a web site.

To get the file, make a GET request to the root of the web service,
and ask for a WADL representation:

{{{
    GET /beta/ HTTP/1.1
is a thin wrapper on top of a generic WADL library: it becomes a
Launchpad library when it reads in Launchpad's WADL file.

Almost every interesting aspect of the web service is described in
this document. You can use it as a basis for your own tools that talk
to Launchpad. It's analogous to the HTML forms you use to manipulate a
web site, and it makes it possible to build tools that are loosely
coupled to the design of the web service.

The WADL document that describes Launchpad's resources is located at the root of the web service: '''https://api.staging.launchpad.net/1.0/'''. You'll need to request a WADL
representation instead of the JSON one we retrieved in the first part
of this tutorial:

{{{
    GET /1.0/ HTTP/1.1
    Host: api.staging.launchpad.net
Line 683: Line 816:
Every entry resource has a 'resource_type_link' that's an index into
this document. "http://api.launchpad.net/beta/#person", for instance,
is a reference to the XML tag in this document with the ID
By Launchpad convention, every entry resource has a
'resource_type_link' that's an index into this
document. "http://api.staging.launchpad.net/1.0/#person", for
instance, is a reference to the XML tag in this document with the ID
Line 687: Line 821:
resource, and it's the value of 'resource_type_link' in the JSON
representation of every "person" resource.

What's not defined in this file? Mainly, there's also a lack of
information
about our URL structure. You've already seen that you can
get a
description of any person in Launchpad by sending GET to
http://api.launchpad.net/beta/~{name} and plugging in the name. This
is a useful shortcut that can often save you a few HTTP requests, but
the WADL file doesn't say anything about that. It's possible to put
this information into WADL; we just haven't implemented it yet.
resource, and it's what you'll find as 'resource_type_link' in the
JSON representation of every "person"-type resource.

What's not defined in this file? Mainly, there's a lack of information
about our URL structure. You've already seen that you can get a
description of any person in Launchpad by sending GET to
http://api.staging.launchpad.net/1.0/~{name} and plugging in the
name. This is a useful shortcut that can often save you a few HTTP
requests, but the WADL file doesn't say anything about that. It's
possible to put this information into WADL; we just haven't
implemented it yet.

You can get a WADL representation of most individual resources by
sending an appropriate GET request to the resource's URL:

{{{
    GET /1.0/~my-user-account HTTP/1.1
    Host: api.staging.launchpad.net
    Accept: application/vd.sun.wadl+xml
}}}

You'll get back a small WADL document that contains a reference to the
large WADL document at the service root. This can be useful if you're
lost and need to get back on track, or if you don't want to rely on
the Launchpad-specific 'resource_type_link' convention.

= Miscellaneous tips and traps =

 * When you pass parameters to methods, it is necessary to be aware of lazr.restful's data type marshalling - basically, data is represented as if it were fragments of JSON, which means string parameters need quotes around them.

Hacking the Launchpad web service

Canonical provides a Python client, launchpadlib, for reading and writing to Launchpad's web service. But there are many situations where you wouldn't use launchpadlib: if you're not a Python programmer, if you want to write an Ajax client that runs in a web browser, if launchpadlib is too heavyweight for what you want to do, or if you just want to understand what's going on between the client and the server.

This document describes the HTTP resources published through Launchpad's web service. It shows you how to read and write information about those resources by making HTTP requests. It assumes you have basic knowledge of how a web browser and web server interact.

Launchpad Resources

Every object in Launchpad--everything you might think of as having a separate identity--has its own URL in the Launchpad web service. You can bookmark this URL and pass it around. You can also manipulate the underlying Launchpad object by making HTTP requests to its URL. Everything means everything: big things like people, teams, bugs, bug tasks, and projects, all the way down to team memberships, bug watches, and the languages people speak.

(This is the plan, anyway. We're still working to expose all of Launchpad's objects through the web service, and many objects that are exposed don't yet publish much useful information.)

In the following sections I'll show you HTTP requests you can make and the responses you'll get back.

I'll be skipping over the fact that to make authenticated requests, you'll need to digitally sign those requests using a set of OAuth credentials.

Unauthenticated requests can see all public objects, which are the majority, but they can't see private objects (such as hidden email addresses, security-sensitive bugs, or private teams), they can't change anything, and of course they have no concept of a "me" resource to start from. Trying to access a private resource will give you a "401 Unauthenticated" error.

To see how to sign a request, see "Signing Requests".

The homepage

Launchpad's website has a homepage that acts as a jumping-off point to projects, bugs, people, answers, and so on. The web service has a homepage, too. It's a lot more sparse than the web site's homepage but it also makes a good jumping-off point.

The root of the web service is http://api.launchpad.net/1.0/. I'll show you the staging server for these examples so you don't accidentally change something you don't mean to change. To get the service's homepage, just sent an HTTP GET request to /1.0.

    GET /1.0 HTTP/1.1
    Host: api.staging.launchpad.net

The response you get back will look something like this:

    200 OK
    Content-type: application/json

    {"people_collection_link": "http:\/\/api.staging.launchpad.net\/1.0\/people", "bugs_collection_link": "http:\/\/api.staging.launchpad.net\/1.0\/bugs", "me_link": "http:\/\/api.staging.launchpad.net\/people/+me"}

That's a JSON document, which you can turn into a native-language data structure using whatever libraries are available for the programming language you're using. (In launchpadlib, we use the Python simplejson library to process JSON documents.) Process it and you'll have something like this Python data structure:

    { u"people_collection_link":
        u"http://api.staging.launchpad.net/1.0/people",
      u"bugs_collection_link":
        "http://api.staging.launchpad.net/1.0/bugs",
      u"me_link":
        "http://api.staging.launchpad.net/people/+me"}

(Note: If you're following along and you sent a GET to /1.0/, you'll see that the "me_link" isn't actually present yet. The URL works, it's just that the web service homepage doesn't link to it yet. We're going to add that link soon, and it's very useful for purposes of discussion, so for now just pretend it's there.)

Almost all of the documents served through the Launchpad web service are JSON documents. In fact, almost all of them describe dictionaries of key-value pairs, like this one.

The Launchpad website's homepage contains links to other parts of Launchpad. This homepage serves the same purpose: it tells you about the major parts of the Launchpad web service. Once you've got the hang of the web service you can bypass the web service's homepage, just as you might bypass the Launchpad homepage, but it's a good place to start.

This document contains three links, and by following the links you can explore the web service. As we expose more of Launchpad through the web service, you'll see more links added to this document.

  • people_collection_link: This is the list of people tracked by Launchpad.
  • bugs_collection_link: This is the list of bugs tracked by Launchpad.
  • me_link: This is a link to your user account.

By convention, all fields that are links to other parts of the Launchpad web service have names ending in '_link'. Other fields might have URLs as values, and you can follow those URLs if you want, but they probably don't have anything to do with the Launchpad web service.

Apart from the homepage, which you've just seen, there are two basic types of objects in launchpad: entries and collections. An entry is a single object, like a bug or your user account; a collection is a group of entries. Links to collections always have names that end in "_collection_link"; links to entries always have names that just end in "_link".

An entry: your user account

If you follow the "me_link" by making a GET request to http://api.staging.launchpad.net/people/+me...

    GET /1.0/people/+me HTTP/1.1
    Host: api.staging.launchpad.net

...you'll be redirected to the Launchpad web service's view of your user account.

    302 Found
    Location: http://api.staging.launchpad.net/~your-user-name

Note the symmetry with the Launchpad website. If you visit http://www.launchpad.net/people/+me in your web browser, you'll be redirected to http://www.launchpad.net/~your-user-name.

Your user account is an entry-type resource. It responds to a specific HTTP interface that's common to all entry resources exposed by the Launchpad web service: GET, PUT, and PATCH. Entry resources may also respond to custom named GET and POST operations which are different for every kind of entry; we'll cover those later.

Reading resources: GET

The most basic operation on an entry resource is GET. To find out about your user account you send a HTTP GET request to that URL:

   GET /1.0/~your-user-name HTTP/1.1
   Host: api.staging.launchpad.net

You'll get back a response document containing a JSON hash, just like you did when you sent GET to the service root. Here's what the hash looks like when I convert it to a Python dictionary.

{u'admins_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/admins',
 u'confirmed_email_addresses_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/confirmed_email_addresses',
 u'date_created': u'2005-06-06T08:59:51.619713+00:00',
 u'deactivated_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/deactivated_members',
 u'display_name': 'Your name here',
 u'expired_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/expired_members',
 u'hide_email_addresses': False,
 u'homepage_content': None,
 u'indirect_participations_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/indirect_participations',
 u'invited_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/invited_members',
 u'irc_nicknames_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/irc_nicknames',
 u'is_team': False,
 u'is_valid': False,
 u'jabber_ids_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/jabber_ids',
 u'karma': 0,
 u'languages_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/languages',
 u'latitude': None,
 u'longitude': None,
 u'mailing_list_auto_subscribe_policy': u'Ask me when I join a team',
 u'members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/members',
 u'members_details_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/members_details',
 u'memberships_details_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/memberships_details',
 u'mugshot_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/mugshot',
 u'name': u'your-user-name',
 u'open_membership_invitations_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/open_membership_invitations',
 u'participants_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/participants',
 u'participations_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/participations',
 u'preferred_email_address_link': u'http://api.staging.launchpad.net/~your-username/+email/your.address@foo.com',
 u'proposed_members_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/proposed_members',
 u'resource_type_link': u'http://api.staging.launchpad.net/1.0/#person',
 u'self_link': u'http://api.staging.launchpad.net/1.0/~your-user-name',
 u'sub_teams_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/sub_teams',
 u'super_teams_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/super_teams',
 u'team_owner_link': None,
 u'time_zone': None,
 u'visibility': u'Public',
 u'wiki_names_collection_link': u'http://api.staging.launchpad.net/1.0/~your-user-name/wiki_names'}

That's a lot of information. You can consult the reference documentation for more information on what each of the fields of this hash mean. What's important is that there are three and only three kinds of fields:

  1. Atomic chunks of data. Examples here include 'date_created', 'display_name', and 'time_zone'. These may be of any JSON data type. Some of these can be modified: you can change your own 'display_name', but you can't change 'date_created'. (How do you know which fields can be modified? See "WADL Description" below.)
  2. Links to other entry-type resources. These work the same way as the "me_link" in the JSON representation of the Launchpad server root. 'mugshot_link' points to your mugshot image. 'preferred_email_address_link' points to a resource that represents your preferred email address. Remember, every object in Launchpad has its own URL, even tiny objects like email addresses and languages. Again, by Launchpad convention, all links between resources have field names that end in '_link'. Two of these links are especially important, and you'll find them present in every representation of an entry-type resource.
    • 'self_link' is the URL to the resource itself. You can keep track of this URL and come back to it later to find this resource again. It's just like bookmarking a web page.
    • 'resource_type_link' is a link to a machine-readable description of this resource. You can use this to do introspection on the resource, finding out what special operations are available, or which of the fields in the representation can be modified. For more on this see "WADL Description" below.
  3. Links to collection-type resources. A person in Launchpad can be associated with more than one email address, but only one of those can be the 'preferred' address at any one time. The 'preferred_email_address_link' field points to whatever address is currently preferred. The 'confirmed_email_addresses_collection_link' field points to a list containing all the addresses. For more on collections, see "The list of bugs" below.

Modifying resources: PUT

(To make any changes, you must send a signed request.)

It's your user account; you should be able to change it through the web service. The simplest way to do this is to take the document you received from a GET request, modify it so that it says what you want, and send it back to the server with a PUT request.

Let's say I want to change my display name. The document I got in the previous section looks like this:

    {
       ...
      u'display_name': 'Your name here',
       ...
    }

Since I parsed that document into a data structure (call it 'person'), it's easy for me to change that data structure with code. Here's Python code that will work:

    person['display_name'] = 'New display name'

Then I can turn the data structure back into a JSON string. Now the string looks like this:

    {..., "display_name": "New display name", ...}

Now I can send the document back to the server with PUT:

    PUT /1.0/~your-user-name HTTP/1.1
    Host: api.staging.launchpad.net
    Content-type: application/json

    {..., "display_name": "New display name", ...}

The response should indicate that the changes were made:

    200 OK

Modifying resources: PATCH

The PUT technique is very convenient when you already have a document describing the resource you want to modify. If you don't have such a document, you don't have to create the whole thing. You can create a smaller document from scratch, and only mention the fields you want to change:

    {"display_name": "New display name"}

You can send this document to the server as part of a PATCH request:

    PATCH /people/~your-user-name HTTP/1.1
    Host: api.staging.launchpad.net
    Content-type: application/json

    {"display_name": "New display name"}

Again, the response should be simple:

    200 OK

Some of a resource's fields are links to other resource: for instance, your preferred email address.

    print person['preferred_email_address_link']
    # http://api.staging.launchpad.net/~your-username/+email/your.address@foo.com

Since the value is shown as a URL, you change the value by changing the URL. In this PATCH request I change my preferred_email_address_link so that it points to another of the 'email address' type resources associated with my user account.

    PATCH /people/~{your-user-name} HTTP/1.1
    Host: api.staging.launchpad.net
    Content-type: application/json

    {"preferred_email_address_link":
     "http://api.staging.launchpad.net/~your-username/+email/another.address@bar.com"}

How did I find that link? Well, you can get a collection of all your confirmed email addresses by following the "confirmed_email_addresses_collection_link". (See "the list of bugs" below to learn what a collection looks like.) Each email address has its own permanent URL, accessible as its 'self_link' field. Look through the collection, find the address you want, and stick its 'self_link' URL into the document describing your user account. Then you can make a request that changes which email address is your 'preferred' one. In Python code the link change might look like this:

    person['preferred_email_address_link'] = new_email_address['self_link']

Then you'd make a PUT or PATCH request to send the change to the server.

The WADL description (again, see below) tells you which links you're allowed to modify. This information is also in the reference documentation.

You can never change a link to a collection. The link to the collection of your confirmed email addresses will always be "http://api.staging.launchpad.net/1.0/~{your-user-name}/confirmed_email_addresses".

Error handling

If something goes wrong with your request, you'll probably get a response code of 400 ("Bad Request") instead of 200 ("OK"). The body of the response will tell you what was wrong with your request. For instance, if you try to send PUT or PATCH data in a format other than JSON...

    PATCH /people/~{your-user-name} HTTP/1.1
    Host: api.staging.launchpad.net

    display_name=New display name

...you'll get this response:

    400 Bad Request
    Content-type: text/plain

    Entity-body was not a well-formed JSON document.

A collection: the list of bugs

The user account resource is a typical example of an entry-type resource: one that represents one specific thing. The other main sort of resource in the Launchpad web service is a collection-type resource: one that acts as a container for a number of other resources.

As with entry resources, every container resource has its own URL that you can bookmark or pass around. Send a GET request to a container resource, and you'll get you a JSON document describing the collection.

One interesting collection is the collection of filed bugs. Remember the homepage of the Launchpad web service? The 'bugs_collection_link' there is the URL to the collection of bugs.

Send a GET request to that URL...

    GET /1.0/bugs HTTP/1.1
    Host: api.staging.launchpad.net

...and you'll get back a JSON document that looks like this:

    {
      u'total_size' : 252673,
      u'next_collection_link' :
        u'http://api.staging.launchpad.net/1.0/bugs?ws.start=75&ws.size=75',
      u'resource_type_link' : u'http://api.launchpad.dev/1.0/#bugs',
      u'entries' : [ ... ]
    }

All collection resources serve JSON documents that look like this, whether they're collections of bugs, people, bug tasks, email addresses languages, or whatever. It's always a JSON hash with keys called 'total_size', 'resource_type_link', and 'entries'. The 'total_size' field is the number of items in the collection, 'resource_type_link' is a machine-readable description of the collection (see "WADL Description" below). The 'entries' field contains the actual entries.

Except of course it doesn't contain *all* the entries. Putting over 250,000 bugs in one document would be crazy. Launchpad's web service does the same thing as the Launchpad website: it sends you one page of bugs at a time, and includes links (where appropriate) to the next and previous pages. So the 'entries' field here is a JSON list containing 75 JSON hashes, each describing one bug. Each hash contains the same information as if you'd sent a GET request to that bug's 'self_link'.

If you need more than 75 bugs, you can send a GET request to the 'next_collection_link'. If you need some other number of bugs, or you want to start from item 20 in the list instead of the first item, you can manually vary the 'ws.start' and 'ws.size' parameters. Sending a GET request to http://api.staging.launchpad.net/1.0/bugs?ws.start=9&ws.size=3 would get you three bugs: the ones that would be accessible from "collection['entries'][9:12]" if you'd sent GET to http://api.staging.launchpad.net/1.0/bugs and retrieved the first 75.

For consistency's sake, _all_ collection resources serve JSON hashes with 'total_size' and the rest, even collections which are very unlikely to have more than 75 entries, like someone's list of spoken languages.

Named operations

All entry resources support GET, PUT, and PATCH. All collection resources support GET. There are also custom operations available on specific resources. We call these "named operations" because they're identified by name rather than by the name of one of the standard HTTP methods.

These operations are described in the reference documentation (and in the WADL file), and they're different for every kind of resource, so I won't cover them all here. What I will do is give a couple examples and talk about what all named operations have in common.

A named operation either modifies the Launchpad data set or it doesn't. If it's read-only, then you access it with HTTP GET. If it's a write operation, you need to access it with HTTP POST.

Read operations (GET)

The person search operation is a good example of a read operation. Launchpad exposes a list of people at http://api.staging.launchpad.net/1.0/people, but for most applications you don't want to page through the user accounts the way you would on the Launchpad person list. Usually you want to _filter_ that huge list to find specific people.

To invoke the person search operation you make a GET request to this URL:

    http://api.staging.launchpad.net/1.0/people?ws.op=find&text={text}

where "{text}" is the text you want to search for.

(Again, you can find out about this named operation by reading the reference documentation or the WADL definition of http://api.staging.launchpad.net/1.0/people. There's no secret here.)

The response to a read operation can be any JSON document, but it's usually a JSON hash that looks exactly like the JSON representation of a collection resource. It's got 'total_size', 'entries', possibly 'next_collection_link', and so on. So getting http://api.staging.launchpad.net/1.0/people?ws.op=find&text=foo gives you the same kind of document as getting http://api.staging.launchpad.net/1.0/people, but there'll be a lot less data to process.

In general, you invoke a named operation on a resource by tacking the query parameter "ws.op={operation name}" onto the resource's URL. In this case, the resource was the collection of people and the name of the operation was "find". It's just like calling a method in a programming language: the resource is the object and the operation is the method. Any arguments to the method are appended as additional query parameters.

Write operations (POST)

Team creation is a good example of a write operation. Launchpad treats teams the same as people, so when you create a team you're adding to the list of people. To invoke the team creation operation you make a POST request to the list of people:

    POST /1.0/people HTTP/1.1
    Host: api.staging.launchpad.net
    Content-Type: application/x-www-form-urlencoded

    ws.op=newTeam&name={name}&display_name={display_name}

Where {name} is the name you want for the new team, and {display_name} is how you want the team to be described. It's the same as for a read operation, except all your query arguments go into the body of the POST instead of into the URL.

Like read operations, write operations can return any JSON document. Most often, they return nothing--only a status code of 200 ("OK") to show that the operation was carried out. But operations that create new Launchpad objects, like newTeam, do something different. If you manage to create a team you'll see a response that looks like this:

    201 Created
    Location: http://api.staging.launchpad.net/1.0/~{name}

That's your indication that the team was created, and that you can find the new team at http://api.staging.launchpad.net/1.0/~{name}. Now you can go over to the new team and make additional HTTP requests to customize it, add memberships, and so on. In general, Launchpad's web service gives you the URLs to newly minted resources, rather than making you guess them.

A hosted file: a user's mugshot

The fourth type of resource is the hosted file. This resource is a front-end to a file stored in Launchpad's file library. The example I'll use is a person's mugshot image. You can find the URL to this resource by looking under 'mugshot_link' in the JSON representation of a person. It should look like "/1.0/~{person}/mugshot".

Read (GET)

When you send a GET request to a hosted file resource, you'll get back an HTTP redirect to a file in Launchpad's file library.

    GET /1.0/~salgado/mugshot HTTP/1.1
    Host: api.staging.launchpad.net

You'll get back a response that looks like this:

    303 See Other
    Location: Location: http://staging.launchpad.net:58000/92/mugshot

Send a second GET request to the URL in Location, and you'll get the image document itself.

    GET /92/mugshot HTTP/1.1
    Host: staging.launchpad.net:58000

    200 OK
    Content-Type: image/jpeg

    [image goes here]

Write (PUT)

To modify a hosted file resource, send a PUT request to its URL. (This is the "/1.0/~{person}" URL on api.*.launchpad.net, not the library URL you get back as a redirect.) Make sure to set the Content-Type header to the MIME type of the file you're writing. You can also set the Content-Disposition header to specify the server-side filename of the file. Here's how to change a person's mugshot.

    PUT /1.0/~salgado/mugshot HTTP/1.1
    Host: api.staging.launchpad.net
    Content-Type: image/png
    Content-Disposition: attachment; filename=my-mugshot.png

    [image goes here]

Error handling

Launchpad may enforce restrictions on the files you write. For instance, a mugshot must be an image file, and the image must have a specific height and width. If you send a bad file to Launchpad, you'll get a response that looks something like this.

    400 Bad Request

    The file uploaded was not recognized as an image; please
    check it and retry.

Delete (DELETE)

To delete a hosted file, send a DELETE request to its URL:

    DELETE /1.0/~salgado/mugshot HTTP/1.1
    Host: api.staging.launchpad.net

This will not necessarily delete the file from the Launchpad library, because there might be other references to it within Launchpad. But the file will be disassociated from the hosted-file resource. In this case, the "salgado" user will stop having a mugshot, and any future attempts to GET /1.0/~salgado/mugshot will return HTTP 404 ("Not Found").

Using the reference documentation

Throughout this document I've revealed seemingly secret information about the capabilities of various resources. It makes intuitive sense that you should send a GET to a resource's URL to find out more about it, but how are you supposed to know that you can also send a GET to that URL plus "?ws.op=find"? The HTTP standard says (more or less) that if you PUT a document to a resource that supports PUT, the server should try to apply your new document to the underlying dataset. But how are you supposed to know that you're allowed to modify a person's "latitude" but not their "karma"?

If you don't know the capabilities of a resource, you can look it up in the reference documentation. First, look at the resource's 'resource_type_link'. It'll be something like "http://api.launchpad.dev/1.0/#bugs". Take the anchor part of that URL (here, "#bugs"), and use it as an anchor into the reference documentation.

That is, if the 'resource_type_link' is "http://api.launchpad.dev/1.0/#bugs", you can find human-readable documentation about that resource by going to https://launchpad.net/+apidoc/devel.html#bugs in your web browser.

The reference documentation will tell you about all the fields in an object's JSON representation, and all the HTTP methods it will respond to.

Two ways to save time and bandwidth

Ask for compressed documents

Launchpad's web service serves XML and JSON documents that compress very well. You'll get the documents faster and save bandwidth if you ask Launchpad to compress documents before sending them over the network.

You do this by specifying a compression algorithm in the "TE" request header. Launchpad's web service supports two compression algorithms: "gzip", the standard gzip algorithm handled by Python's gzip module, and "deflate", the algorithm handled by Python's zlib module. Both of these are as defined in the HTTP standard.

So your TE header will look like this:

  TE: deflate

or like this:

  TE: gzip

Launchpad will send you compressed data, and will set the Transfer-Encoding response header to the name of the compression algorithm it used. It'll either look like this:

   Transfer-Encoding: deflate

or like this:

   Transfer-Encoding: gzip

Most web servers use the Accept-Encoding and Content-Encoding headers to handle compression. This isn't technically wrong, but it interferes with other optimizations we want to make, so we do things differently.

Cache the documents you get

It's important that you cache the documents you get from Launchpad, especially documents like the WADL file that are large and don't change very often.

Here's how httplib2 does caching (launchpadlib is based on httplib2). When it makes a GET request and gets a document back, it stores the document in a file, along with all of the HTTP response headers. The next time it needs to make that GET request, it uses the cached response instead of making another request to get the same data back.

But how do you know that the document hasn't secretly changed since the last time you retrieved it? Here's how to check without making the same request again. When you get a document from Launchpad, you'll also get a value for the HTTP response header "ETag". It'll look like this:

    ETag: "924eb0c15c911d64e633b5f012d046d04a83b571"

If you suspect the document has changed, make a GET request to the document's URL, but include the ETag in the HTTP header "If-None-Match".

    If-None-Match: "924eb0c15c911d64e633b5f012d046d04a83b571"

If the document has changed, this will work just like a normal GET request. You'll get back the changed version of the document, including a new ETag. But if the document is the same as it used to be, you'll get an HTTP response that looks like this:

    304 Not Modified

Instead of sending the document again, Launchpad is telling you that you already have the most recent version. This is called "conditional GET", and if you need more detail there's a lot more information about it on the web.

Launchpad doesn't serve ETags for collections, only for individual objects and for the server root.

Avoid stepping on other peoples' toes

Let's say I'm using the Launchpad website to change the details of a bug, and you're using Launchpad's web service. I change the details of a bug. A few seconds later, you make a contradictory change to the same bug. You've overwritten my change without even knowing about it.

Here's how to avoid that problem. Remember the ETags from the previous section? Whenever you make a PUT or PATCH request to an object, include that object's ETag in the "If-Match" HTTP header.

    If-Match: "924eb0c15c911d64e633b5f012d046d04a83b571"

If no one has made a change to this object, your PUT or PATCH will go through. If someone else made a change to the object that you haven't seen, you'll get an HTTP response that looks like this:

    412 Precondition Failed

Launchpad is telling you that it didn't make your change because another change happened that you don't know about. You'll have a chance to GET the new version of the object (complete with a new ETag), work out any contradictions between the other person's change and the change you want to make, and re-submit with the new ETag.

WADL Description

Like most web service providers we publish a prose document describing the capabilities of all our resources. But we also publish a machine-readable document containing the same information. It's written in WADL format, and you can use it as a basis for tools that interact with the web service. In fact, the reference documentation is just a human-readable transformation of the WADL document. The launchpadlib Python library is a thin wrapper on top of a generic WADL library: it becomes a Launchpad library when it reads in Launchpad's WADL file.

Almost every interesting aspect of the web service is described in this document. You can use it as a basis for your own tools that talk to Launchpad. It's analogous to the HTML forms you use to manipulate a web site, and it makes it possible to build tools that are loosely coupled to the design of the web service.

The WADL document that describes Launchpad's resources is located at the root of the web service: https://api.staging.launchpad.net/1.0/. You'll need to request a WADL representation instead of the JSON one we retrieved in the first part of this tutorial:

    GET /1.0/ HTTP/1.1
    Host: api.staging.launchpad.net
    Accept: application/vd.sun.wadl+xml

By Launchpad convention, every entry resource has a 'resource_type_link' that's an index into this document. "http://api.staging.launchpad.net/1.0/#person", for instance, is a reference to the XML tag in this document with the ID "person". That's the tag describing the capabilities of a "person" resource, and it's what you'll find as 'resource_type_link' in the JSON representation of every "person"-type resource.

What's not defined in this file? Mainly, there's a lack of information about our URL structure. You've already seen that you can get a description of any person in Launchpad by sending GET to http://api.staging.launchpad.net/1.0/~{name} and plugging in the name. This is a useful shortcut that can often save you a few HTTP requests, but the WADL file doesn't say anything about that. It's possible to put this information into WADL; we just haven't implemented it yet.

You can get a WADL representation of most individual resources by sending an appropriate GET request to the resource's URL:

    GET /1.0/~my-user-account HTTP/1.1
    Host: api.staging.launchpad.net
    Accept: application/vd.sun.wadl+xml

You'll get back a small WADL document that contains a reference to the large WADL document at the service root. This can be useful if you're lost and need to get back on track, or if you don't want to rely on the Launchpad-specific 'resource_type_link' convention.

Miscellaneous tips and traps

  • When you pass parameters to methods, it is necessary to be aware of lazr.restful's data type marshalling - basically, data is represented as if it were fragments of JSON, which means string parameters need quotes around them.

API/Hacking (last edited 2024-07-15 14:52:38 by jugmac00)