ExposureManager::API - A programmable interface to ExposureManager uploads
ExposureManager provides a programmable interface to galleries, photos, image uploads and processing. It is inspired by ReST (http://www.xml.com/pub/a/2004/12/01/restful-web.html), which makes it light-weight and easy to implement. All you need is a HTTP library and an XML or JSON library. Example code in Perl is provided to help you get a feel for it.
The API accepts both XML and JSON, and emits the same type as your input,
with the exception of the file-upload URI. We only accept standard HTML
upload there. We look at the accept and content-type headers to determine
which format you want. If you're unable to set either, you can use a query
parameter named format with a value of xml or json. This query param
will take precedence over the http headers.
Since this API is not meant for public consumption, we require all
interaction to be authenticated. You log the user in through the login
URI. That will return a session cookie, as well as a session_id in the
response. Any subsequent API call will either need to supply the session
cookie, or a URL parameter with the name SID.
Use of https is recommended.
Note: Variables in URIs or XML or JSON will be written as ${variable_name}.
The base URI for our API is https://www.exposuremanager.com/rest/.
URI: https://www.exposuremanager.com/rest/
METHOD: GET
Output: A (terse) overview of the directly accessible resources.
Currently the JSON output looks like this:
{
"resources" : {
"documentation" : "https://www.exposuremanager.com/api-doc/index.html"
"login" : {
"methods" : [ "POST" ],
"uri" : "https://www.exposuremanager.com/rest/login"
},
"account" : {
"method" : [ "GET" ],
"uri" : "https://www.exposuremanager.com/rest/account"
},
"upload" : {
"methods" : [ "GET", "POST" ],
"uri" : "https://www.exposuremanager.com/rest/upload"
},
"upload_locations" : {
"methods" : [ "GET" ],
"uri" : "https://www.exposuremanager.com/rest/upload_locations"
},
"uploadjobs" : {
"methods" : [ "GET", "POST" ],
"uri" : "https://www.exposuremanager.com/rest/uploadjobs"
},
"galleries" : {
"methods" : [ "GET" ],
"uri" : "https://www.exposuremanager.com/rest/galleries"
},
"outstanding_orders" : {
"methods" : [ "GET" ],
"uri" : "https://www.exposuremanager.com/rest/orders/outstanding"
}
}
}
URI: https://www.exposuremanager.com/rest/login
METHOD: POST
XML:
Input:
XML: <auth login="${username}" password="${password}" />
JSON: { "auth" : { "login" : "${username}", "password" : "${password}" } }
Output:
XML: <auth message="login successful" session_id="0123456789abcdef0123456789abcdef" status="OK" />
JSON: { "auth" : { "session_id" : "012345...", "message" : "login successful", "status" : "OK" } }
This resource displays basic info about the user account.
URI: https://www.exposuremanager.com/rest/account
METHOD: GET
Input: None
Output:
JSON: { "account" : {
"href" : "public URL",
"admin_href" : "admin URL",
"account_type" : "Premium",
"full_name" : "John Doe",
"last_name" : "Doe",
"first_name" : "John",
"event_model" : 1
}
}
The event_model field is a boolean. It's false for older accounts, and true for accounts that have the event model for organizing photos.
Represents the complete tree of galleries
URI: https://www.exposuremanager.com/rest/galleries
METHOD: GET
Input: none
Output:
XML: <galleries session_id="0123456789abcdef0123456789abcdef">
<gallery name="Main Gallery" uri="https://www.exposuremanager.com/rest/gallery/123">
<gallery name="Sports" uri="https://www.exposuremanager.com/rest/gallery/234" />
<gallery name="Weddings" uri="https://www.exposuremanager.com/rest/gallery/345" />
</gallery>
</galleries>
JSON: {
"galleries" : {
"session_id" : "012345...",
"gallery" : {
"name" : "Main Gallery",
"uri" : "https://www.exposuremanager.com/rest/gallery/123",
"gallery" : [
{
"name" : "Sports",
"uri" : "https://www.exposuremanager.com/rest/gallery/234"
},
{
"name" : "Weddings",
"uri" : "https://www.exposuremanager.com/rest/gallery/345"
}
]
}
}
}
Represents some details of a specific gallery
URI: /rest/gallery/${id}
METHOD: GET
Input: none
Output:
<gallery name="Main Gallery" session_id="..." uri=".../rest/gallery/123" href="public URL">
<photos uri=".../rest/gallery/123/photos" />
<subgallery name="Sports" uri=".../rest/gallery/234" />
<subgallery name="Weddings" uri=".../rest/gallery/345" />
</gallery>
This resource can also be used to create new subgalleries, by posting to it
URI: /rest/gallery/${id}
METHOD: POST
Input: <gallery name="${new_name}" />
Output: A "201 Created" HTTP status with the URI of the new gallery in the Location header
A body like this is provided for convenience:
{
"result": { "status": "Created", "uri": "uri of the new gallery" }
}
At the moment, name is the only supported property for the new gallery.
All other settings will be copied from the parent gallery.
Lists the photos in a gallery. The href attribute points to the web page for the photo. The uri attribute points to the API resource for the photo (see below).
URI: /rest/gallery/${id}/photos
METHOD: GET
Input: None
Output: <photos session_id="...">
<photo
href="http://${user_domain}/p/${short_name}/${photo_id}"
uri=".../rest/photo/${photo_id}"
name="Title of the photo" />
<photo ... />
<photo ... />
</photos>
For POSTing to this resource, see Direct uploads.
This resource contains details of a photo, such as title, caption, author, date, keywords, and comments.
URI: /rest/photo/${photo_id}
METHOD: GET
Input: None
Output: <photo session_id="..."
title="Title of the photo"
caption="Description of the photo"
author="Who took the photo"
date="When the photo was taken"
href="public URL">
<comments uri=".../rest/photo/${photo_id}/comments" />
<keywords>category:value</keywords>
<keywords>category:value</keywords>
</photo>
You can replace any of the title, caption, author, date, and keywords properties by posting a similar document with the new values. Only the properties that you supply will be replaced. The other ones will remain the same.
Note that keywords is an array. The date format for the date field is anything that Date::Manip parses.
METHOD: POST
Input: <photo title="A new title"
caption="A new caption"
/>
or: <photo><keywords>another:keyword</keywords></photo>
Output: a '204 No Content' status
You can delete a photo by using the DELETE verb on this resource:
METHOD: DELETE
Input: None
Output: a '204 No Content' status
Returns a tree of galleries, events, and sections to which you can upload photos.
URI: /rest/upload_locations
METHOD: GET
Input: none
Output: <upload_locations session_id="...">
<location uri=".../rest/gallery/456/photos"
gallery=".../rest/gallery/456"
href="public URL"
name="2011 Baseball"
create_date="2011-03-05T22:34:00">
<location uri=".../rest/gallery/567/photos"
gallery=".../rest/gallery/567"
href="public URL"
name="Spring"
create_date="2011-03-05T22:34:01" />
<location uri=".../rest/gallery/678/photos"
gallery=".../rest/gallery/678"
href="public URL"
name="Summer"
create_date="2011-03-05T22:34:02" />
</location>
</upload_locations>
You can post a photo to one of the locations listed in upload_locations above. It will be added to the gallery immediately, and you will receive the photo URI in return.
URI: /rest/gallery/${id}/photos
METHOD: POST
Input: multipart/form-data file upload (The file field should be named "file")
you can also specify these optional fields:
* print_ready -- 0|1 -- whether this file is fit to print
* rotate -- 0|1 -- rotate based on exif orientation
* position -- 0|1 -- put photo at start (0) of gallery, or end (1)
Output: a '201 Created' status with the URI of the new photo in the Location header
A body like this is provided for convenience:
{
"result": { "status": "Queued", "uri": "uri of the new photo" }
}
This resource allows you to replace the original file with a new one.
URI: /rest/photo/${photo_id}
METHOD: POST
Input: multipart/form-data file upload (The file field should be named "file")
Output: a '204 No Content' status
Represents the contents of the user's upload directory. You can upload files here for later processing.
URI: /rest/upload
METHOD: GET
Input: none
Output:
<upload session_id="...">
<entry size="8" type="file">bar</entry>
<entry type="dir" uri=".../rest/upload/foo">foo</entry>
</upload>
Upload a file
METHOD: POST
Input: multipart/form-data file upload (The file field should be named "file")
Output: <upload session_id="..." status="OK" uri=".../rest/upload" />
Same as above, but for subdirectories. POSTs to an URI with a path component will be stored in the named subdirectory.
Represents a list of active upload jobs
URI: /rest/uploadjobs
METHOD: GET
Input: none
Output:
<uploadjobs session_id="...">
<uploadjob uri=".../rest/uploadjob/1234" />
</uploadjobs>
Create a new upload job:
METHOD: POST
Input:
<uploadjob>
<gallery uri=".../rest/gallery/345" /> # required
<settings
email_account_yn="0|1" # optional, defaults to user config
process_subgalleries_yn="0|1" # optional, defaults to user config
print_ready="0|1" # optional, defaults to user config
rotate="0|1" # optional, defaults to user config
position="0|1" # optional. 0=put images at the start of the gallery, 1=at end
/>
</uploadjob>
Output: redirect to /rest/uploadjob/${new job id}
Represents upload job progress
URI: /rest/uploadjob/${job_id}
METHOD: GET
Input: none
Output:
<uploadjob session_id="...">
<gallery uri="..." />
<progress done_items="12" progress="0.12" status="running" total_items="100" />
<settings email_account_yn="0" position="1" print_ready="1" process_subgalleries_yn="1" rotate="0" />
</uploadjob>
Status can be one of { '', 'enqueued', 'running', 'completed', 'failed' }
These resources allow you to list and fulfill optimized files required for pending orders.
Returns a list of outstanding orders.
URI: /rest/orders/outstanding
METHOD: GET
Input: none
Output:
# empty list
{ "orders" : { "outstanding" : [] } }
# non-empty list
{
"orders" : {
"outstanding" : [
{
"amount" : "15.50",
"customer" : {
"address" : "42 West Ave., AK, 12121, United States",
"name" : "Sue Customer"
},
"date" : "2011-09-18T07:50:53",
"order_id" : "100",
"shipping_method" : "FC",
"uri" : ".../rest/order/outstanding/100"
}
]
}
}
Provides the list of required files. This contains details about the photo, and the products ordered. It also repeats the order details for convenience.
URI: /rest/order/outstanding/${order_id}
METHOD: GET
Input: none
Output:
{
"order" : {
"details" : {
"amount" : "15.50",
"customer" : {
"address" : "42 West Ave. Anchorage, AK, 12121, United States",
"name" : "Sue Customer"
},
"date" : "2011-09-12T07:50:53",
"order_id" : "100",
"shipping_method" : "FC",
"uri" : ".../rest/order/outstanding/100"
},
"required_files" : [
{
"displayname" : "DSC_1234.jpg",
"filename" : "dsc_1234",
"gallery_name" : "2011 Anchorage Marathon",
"order_id" : "100",
"photo_id" : "dsc_1234_10_5",
"products" : [
{
"description" : "8x10",
"orderitem_id" : null,
"product_id" : "50319",
"questions" : [
{
"answer" : "42",
"question" : "BIB no."
},
{
"answer" : "Crop out polar bear",
"question" : "Special instructions"
}
],
"uri" : ".../rest/order/outstanding/100/darazs/50319/"
}
]
}
],
"session_id" : "eb13709627691509b057362353015409"
}
}
This resource receives the optimized file. Use regular HTTP file upload.
URI: /rest/order/outstanding/${order_id}/${photo_details}
METHOD: POST
Input: multipart/form-data file upload (The file field should be named "file")
Output:
# on success, we return HTTP status '201 Created', and a body like this
{ "accepted" : { "file_out" : "some.jpg", "product_name" : "8x10" } }
# on failure, we return HTTP status '406 Not Acceptable, and a body like this:
{ "error" : { "message" : "file resolution is too low, minimum is 0.6MP" } }
use LWP::UserAgent;
use XML::Simple;
# construct ua
my $ua = LWP::UserAgent->new();
$ua->cookie_jar({ file => "t/rest_cookies.txt" });
my $uri = 'https://www.exposuremanager.com/rest';
# login
my $resp = $ua->post($uri. '/login',
Content_Type => 'application/xml',
Content => q{
<auth>
<login>example</login>
<password>secret</password>
</auth>
}
);
Please mail us at rest-api@support.exposuremanager.com for questions, comments and bug reports.
© 2007-2011 ExposureManager. All rights reserved.
THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.