概述

在这份文档中,我们将描述开发者利用又拍API 开发应用涉及到的要点:

基础知识: 开发过程你需要了解的内容, 包括申请API_KEY, 请求地址, 请求格式, 参数, 签名和结果的处理等;

用户认证: 在对基础知识有了初步了解后, 大多数的应用都会需要用户进行认证;

上传照片: 开始了解如何上传照片;

开发示例: 用一个简单的案例演示又拍API的开发

基础知识

申请API KEY

在开始又拍应用之前, 你首先应申请成为我们的合作伙伴, 得到一个成为称之为API_KEY的应用标识和一个用于签名的共享密钥Shared Secret。

请求地址

认证接口 http://www.yupoo.com/services/auth/

上传接口 http://www.yupoo.com/api/upload/

其他接口 http://www.yupoo.com/api/[format]/ - format : rest | xml | json

请求格式

又拍API支持通过REST,JSON和XML三种方式发起请求, 下面分别是三种请求/返回的代码格式 其中xml与rest是一样的

REST 请求

http://www.yupoo.com/api/rest/?method=yupoo.test.echo&name=value

REST 返回

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
    [xml-payload-here]
</rsp>

实例:

http://www.yupoo.com/api/rest/?api_key=d74aad0331494be468c07f47987b099b&method=yupoo.photos.licenses.getInfo

返回:

<rsp stat="ok">
    <licenses>
        <license id="1" name="Attribution License" url="http://creativecommons.cn/licenses/by/1.0/"/>
        <license id="2" name="Attribution-NoDerivs License" url="http://creativecommons.cn/licenses/by-nd/1.0/"/>
        <license id="3" name="Attribution-NonCommercial-NoDerivs License" url="http://creativecommons.cn/licenses/by-nd-nc/1.0/"/>
        <license id="4" name="Attribution-NonCommercial License" url="http://creativecommons.cn/licenses/by-nc/1.0/"/>
        <license id="5" name="Attribution-NonCommercial-ShareAlike License" url="http://creativecommons.cn/licenses/by-nc-sa/1.0/"/>
        <license id="6" name="Attribution-ShareAlike License" url="http://creativecommons.cn/licenses/by-sa/1.0/"/>
    </licenses>
</rsp>

JSON 请求

http://www.yupoo.com/api/json/?method=yupoo.test.echo&name=value

JSON 返回

{"stat":"ok","name":{value}}

实例:

http://www.yupoo.com/api/json/?api_key=d74aad0331494be468c07f47987b099b&method=yupoo.photos.licenses.getInfo

返回:

{"stat":"ok","licenses":[{"id":1,"name":"Attribution License","url":"http:\/\/creativecommons.cn\/licenses\/by\/1.0\/"},
{"id":2,"name":"Attribution-NoDerivs License","url":"http:\/\/creativecommons.cn\/licenses\/by-nd\/1.0\/"},
{"id":3,"name":"Attribution-NonCommercial-NoDerivs License","url":"http:\/\/creativecommons.cn\/licenses\/by-nd-nc\/1.0\/"},
{"id":4,"name":"Attribution-NonCommercial License","url":"http:\/\/creativecommons.cn\/licenses\/by-nc\/1.0\/"},
{"id":5,"name":"Attribution-NonCommercial-ShareAlike License","url":"http:\/\/creativecommons.cn\/licenses\/by-nc-sa\/1.0\/"},
{"id":6,"name":"Attribution-ShareAlike License","url":"http:\/\/creativecommons.cn\/licenses\/by-sa\/1.0\/"}]}

请求参数

每一个接口都有每一个接口特有的参数内容, 这里列出一些每次调用都通用的参数:

名称 是否必需 意思
method 必须 接口方法名, 除上传请求外, 其他请求都必须指定,这里查看如何上传照片
api_key 必须 应用标识
auth_token 必须 用户授权码, 拥有这个授权码, 可以获取用户的照片,个人信息等资料
api_sig 必须 签名, 请参考如何签名

签名

为了确保请求的合法性, 我们对于每个API请求, 都需要进行签名认证。

具体方法如下:

首先排序您的参数列表,如foo=1, bar=2, baz=3 排序后应该是bar=2, baz=3, foo=1

接下来组合Shared Secret和参数名与参数值成一个字符串,如上例是 SHARED_SECRETbar2baz3foo1 参数间的等号 "=" 需要去掉

然后利用MD5摘要算法进行加密, 将得到的值赋给api_sig, api_sig 将作为API请求的参数提交

下面是一个实例:

如果你的API Key是9a0554259914a86fb9e7eb014e4e5d52,shared secret是000005fab4534d05,想要使用的方法是yupoo.auth.getFrob,

那么用来签名的字符串就是:

000005fab4534d05api_key9a0554259914a86fb9e7eb014e4e5d52methodyupoo.auth.getFrob

然后我们通过MD5就计算出签名了。然后在把这个签名值以'api_sig'的名字加到参数中。最后,参数就是这样:

处理返回结果

正确的返回结果

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
    [xml-payload-here]
</rsp>

出错的返回结果

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
    <err code="[error-code]" msg="[error-message]" />
</rsp>

以下为经常出现错误代码及其解释:

代号 消息 解释
96 Invalid signature 签名不正确
97 Missing signature 需要签名,但是没有传递这个参数
98 Login failed / Invalid auth token auth_token未指定或不正确
99 User not logged in / Insufficient permissions 用户未登录或权限不足
100 Invalid API Key API Key不正确
105 Service currently unavailable 服务暂时不可用
112 Method not found 调用的方法不存在
115 Invalid XML-RPC Method Call 无法分析XML-RPC请求
125 Missing required argument 没有指定某些必需的参数
126 API Key expired API Key已经过期
127 Unconfigured API Key API Key未配置
128 User disabled 用户被禁用

用户认证

又拍API 许多接口都需要用户授权才能使用, 开发者在调用需要认证的接口方法时, 都需要传入 auth_token 和 api_sig 两个参数, 请参考基础知识中请求参数的解释说明.

目前来说, 从我们过去与开发者合作来看, 主要有以下几种应用场景:

  1. 基于浏览器的应用;

  2. 桌面应用;

  3. 手机端的应用;

  4. 其他嵌入式终端的应用程序, 例如机顶盒等其他嵌入式数码设备;

对于此前我们提供的认证方式, 我们将继续提供兼容支持, 接下来酱菜用通用的标准认证方式: OAuth, 对于以上应用的认证做出说明:

1.基于浏览器的应用的认证

基于浏览器应用, 可以完全使用oAuth 认证, 通常web应用可以支持绑定多个用户, 开发者应该自行保存auth_token 与用户的关系;

1. 创建登陆链接

下列将以社区API为例

然后生成下面这样的URL:

http://www.yupoo.com/services/auth/?api_key=[api_key]&perms=[perms]&api_sig=[api_sig]

[api_key]是通过我们申请的API key。

[perms]是你的应用所需要的用户帐号访问权限,有以下这些值:

API方法文档里每个API方法都有相应的权限说明,请参考相应文档以确定你的应用所需要的权限。

[api_sig]是其它两个参数的签名(具体请查看如何签名)。

然后定向到生成的URL。

2. 创建认证处理地址

这一步所需要的地址是在你的网站中的地址.同时也就是“Web应用重定向地址”。

在完成认证之后,Yupoo将重定向到你所提供的这个地址,并带上参数frob.下面是一个例子:

您提供的地址是: http://www.example.com/test.jsp
Yupoo将转向这个地址: http://www.example.com/test.jsp?frob=5b5e33a66225a3df41310ae30185927f

3. 利用frob获得token

在您的处理页面种你得到了frob,接下来,你要调用Yupoo!API方法 yupoo.auth.getToken来获取token。 这个方法需要签名(具体请查看如何签名),如果你的API Key是aef4e6a483f0a3fd4d0bd69fb2634234,shared secret是ubpdwr0eszz6vimu, 从上一步得到的frob是5b5e33a66225a3df41310ae30185927f,那么参数就是:

那么用来签名的字符串就是:

ubpdwr0eszz6vimuapi_keyaef4e6a483f0a3fd4d0bd69fb2634234frob5b5e33a66225a3df41310ae30185927fmethodyupoo.auth.getToken

如此一来,我们得到的签名就是对以上字符串做MD5计算得到的值,传值时我们把它的名字写为'api_sig'放到参数中去,最后,参数就是这样:

调用返回结果(以REST返回格式为例,省略头尾):

    <auth>
        <token>402880900c1e0d2b010c1e1333e90001</token>
        <perms>read</perms>
        <user id="f1aff83b7f57ad269dd809238cd7644b" username="caitou" nickname="菜头" />
    </auth>

其中:

4. 调用API 方法文档

当你有了token,就可以调用需要认证的API方法了。比如调用方法yupoo.photos.addTags,参数如下:

别忘了加上api_sig,每个需要认证的方法都需要签名。具体方法见如何签名

2.桌面应用的认证

如果应用有web服务的支持, 可以实现web回调, 我们建议使用标准OAuth的方式来完成认证; 如果应用缺乏web服务的支持, 无法实现web回调, 即无法实现OAuth 认证的全过程, 那么请通过PIN Code认证方式进行

1. 请求一个frob

为了获得会话标识frob, 你要调用Yupoo!API方法 yupoo.auth.getFrob来获取。 这个方法需要签名(具体请查看如何签名),如果你的API Key是9a0554259914a86fb9e7eb014e4e5d52,shared secret是000005fab4534d05, 那么参数就是:

那么用来签名的字符串就是:

000005fab4534d05api_key9a0554259914a86fb9e7eb014e4e5d52methodyupoo.auth.getFrob

然后我们通过MD5就计算出签名了。然后在把这个签名值以'api_sig'的名字加到参数中。最后,参数就是这样:

调用返回结果(以REST返回格式为例,省略头尾):

<frob>402880900a3bb882010a3bb8c00e0001</frob>

其中:

2. 创建登陆链接

然后生成下面这样的URL:

http://www.yupoo.com/services/auth/?api_key=[api_key]&frob=[frob]&perms=[perms]&api_sig=[api_sig]

[api_key]是向又拍申请得到的API key。

[frob]是上面一步里返回得到的

[perms]是你的应用所需要的用户帐号访问权限,有以下这些值:

API方法文档里每个API方法都有相应的权限说明,请参考相应文档以确定你的应用所需要的权限。

[api_sig]是其它两个参数的签名(具体请查看如何签名)。

应用程序打开浏览器并定向到生成的URL。然后应用程序可以切换到用户确认完成在Yupoo上的认证的界面。

3. 获取token

当用户在Yupoo上完成认证回到您的应用程序并点击按钮确认后, 你要调用Yupoo!API方法 yupoo.auth.getToken来获取来获取token。 这个方法需要签名(具体请查看如何签名),如果你的API Key是9a0554259914a86fb9e7eb014e4e5d52,shared secret是000005fab4534d05, 一开始获得的frob是402880900a3bb882010a3bb8c00e0001 那么参数就是:

那么用来签名的字符串就是:

000005fab4534d05api_key9a0554259914a86fb9e7eb014e4e5d52methodyupoo.auth.getTokenfrob402880900a3bb882010a3bb8c00e0001

如此一来,我们得到的签名就是对以上字符串做MD5计算得到的值,传值时我们把它的名字写为'api_sig'放到参数中去,最后,参数就是这样:

调用返回结果(以REST返回格式为例,省略头尾):

<auth>
    <token>402880900c1e0d2b010c1e1333e90001</token>
    <perms>read</perms>
    <user id="f1aff83b7f57ad269dd809238cd7644b" username="caitou" nickname="菜头" />
</auth>

其中:

4. 调用API 方法文档

当你有了token,就可以调用需要认证的API方法了。比如调用方法yupoo.photos.addTags,参数如下:

别忘了加上api_sig,每个需要认证的方法都需要签名。具体方法见如何签名

3.手机应用的认证

1. 获取mini-token

让你的用户访问刚刚得到的认证地址。你可以在你的应用程序下载页面放上这个认证地址并提醒用户在使用此应用之前访问,或者在应用程序里提醒用户访问。

当用户在yupoo上完成认证,他们将会得到一个9位数字,例如:123-456-789

然后他们回到应用程序,这时你的应用程序界面应该让用户输入这9位的mini-token。(中间的"-"号可以省略,主要看你自己的习惯)

2. 根据mini-token,获得token

为了获得token, 你要调用Yupoo!API方法 yupoo.auth.getFullToken。因为token才能用于调用要认证的API方法。这个方法需要签名(具体请查看如何签名 ),如果你的API Key是9a0554259914a86fb9e7eb014e4e5d52,shared secret是000005fab4534d05, mini-token是123-456-789 那么参数就是:

那么用来签名的字符串就是:

000005fab4534d05api_key9a0554259914a86fb9e7eb014e4e5d52methodyupoo.auth.getFullTokenmini_token123-456-789

如此一来,我们得到的签名就是对以上字符串做MD5计算得到的值,传值时我们把它的名字写为'api_sig'放到参数中去,最后,参数就是这样:

调用返回结果(以REST返回格式为例,省略头尾):

<auth>
    <token>402880900c1e0d2b010c1e1333e90001</token>
    <perms>read</perms>
    <user id="f1aff83b7f57ad269dd809238cd7644b" username="caitou" nickname="菜头" />
</auth>

其中:

3. 调用API 方法

当你有了token,就可以调用需要认证的API方法了。比如调用方法yupoo.photos.addTags,参数如下:

别忘了加上api_sig,每个需要认证的方法都需要签名。具体方法见如何签名

4.OOB 方式认证

结合手机短信的认证方式 适用于: 机顶盒 / 电视机 / 其他缺乏浏览器支持, 输入不方便的设备

5.Basic Auth

直接通过帐号密码向API发送请求登录。需要特殊开通。

上传照片

本章节描述如何利用外部的程序,将照片上传到Yupoo.

上传地址

上传照片也是通HTTP协议,您需要发送一个HTTP multipart request到以下地址:

http://www.yupoo.com/api/upload/

参数列表

名称 是否必需 意思
photo 必须 二进制照片数据
title 可选 照片的标题
description 可选 照片的故事或者描述
tags 可选 利用空格分隔的标签
album_id 可选 相册ID,如果指定了这个参数可以同时将上传的照片添加到指定相册中
group_id 可选 群ID,如果指定了这个参数可以同时将上传的照片发送到指定的群中
is_public 可选 访问权限,1-开放 0-加密, 如果指定为0,请指定is_family,is_friend,is_contact,如果不指定,默认均为1
is_contact 可选 在is_public指定为0时,生效
is_friend 可选 在is_public指定为0时,生效
is_family 可选 在is_public指定为0时,生效

如果你不是用已有的封装来完成这个动作,那么你要在尾行自己构建成如下形式。

POST /api/upload/ HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------7d44e178b0434
Host: www.yupoo.com
Content-Length: 35261

-----------------------------7d44e178b0434
Content-Disposition: form-data; name="api_key"

3632623532453245
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="auth_token"

436436545
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="api_sig"

43732850932746573245
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="photo"; filename="C:\test.jpg"
Content-Type: image/jpeg

{RAW JFIF DATA}
-----------------------------7d44e178b0434--

boundary必须随机生成,并且不应该在封装内的任何地方产生。最后,不要忘记在JFIF信息后和结尾之前要加上行结尾。

用户认证

这个接口需要认证,并要有write的权限.

如何获取用户认证标识,请看用户认证开发指南

在做签名的过程中,不需要把参数photo参数作为签名的内容

结果返回

调用返回结果(以REST返回格式为例), 成功的相应如下:

<?xml version="1.0" encoding="utf-8"?>
<rsp stat="ok">
    <photos>
        <photo id="9a0554259914a86fb9e7eb014e4e5d52" host="1" dir="20060504" filename="20060504_123123" title="IMG_1234" />
        <photo id="9a0554259914a86fb9e7eb014e4e1231" host="1" dir="20060504" filename="20060504_123123" title="IMG_1235" />
    </photos>
</rsp>

发生错误的相应:

<?xml version="1.0" encoding="utf-8"?>
<rsp stat="fail">
    <err code="[error-code]" msg="[error-message]" />
</rsp>

错误代码

文件上传失败可能的原因是:

代号 解释
2 没有指定上传的文件
3 一般地上传错误
4 文件大小为零
5 文件格式不对
6 超过了流量限制
7 文件尺寸太大
8 文件尺寸太大

开发案例

<?php
class YupooAPI {
    public $api_key;
    public $secret;
    public $perm;
    public $used;
    private static $AUTH_URL = 'http://v.yupoo.com/services/auth/';
    private static $ENDPOINT = 'http://v.yupoo.com/api/json/';
    private static $UPLOAD_URL = 'http://v.yupoo.com/api/upload';
    public $error_code;
    public $error_msg;
    public $die_on_error;
    public $token;
    public function __construct($api_key, $secret, $perm = 'read', $die_on_error = false) {
        $this->api_key = $api_key;
        $this->secret  = $secret;
        $this->die_on_error = $die_on_error;
        $this->perm = $perm;
        $this->used = 'web';                // web / desktop / mobile 
        $this->service = "yupoo";
        //  echo $api_key . ":" . $secret;
        // Call CURL as REQUET METHOD;
        /*
        require_once 'HTTP/Request.php';
        $this->req = new HTTP_Request();
        $this->req->setMethod(HTTP_REQUEST_METHOD_POST);
        */
    }
    private function api_call($method, $args) {
        $args = array_merge(array("method" => $method, "api_key" => $this->api_key), $args);
        /*
        if (!empty($this->token)) {
            $args = array_merge($args, array("auth_token" => $this->token));
        } elseif (!empty($_SESSION['phpyupoo_auth_token'])) {
            $args = array_merge($args, array("auth_token" => $_SESSION['phpyupoo_auth_token']));
        }
        */
        ksort($args);
        $auth_sig = '';
        foreach ($args as $key => $data) {
            $auth_sig .= $key . $data;
        }
        if (!empty($this->secret)) {
            $api_sig = md5($this->secret . $auth_sig);
            $args["api_sig"] = $api_sig; 
        }
        $query_string = http_build_query($args);
        $ch = curl_init(YupooAPI::$ENDPOINT);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.7) Gecko/2009021906 Firefox/3.0.7");
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_COOKIE, "");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        $content = curl_exec($ch);
        curl_close($ch);
        $res_data = json_decode($content, true);
        return $res_data;
    }
    private function auth() {
        $api_sig = md5($this->secret . "api_key" . $this->api_key . "perms" . $this->perm);
        $endpoint = YupooAPI::$AUTH_URL . '?api_key='.$this->api_key.'&perms='.$this->perm.'&api_sig='.$api_sig;
        header("Location: $endpoint");
        exit;
    }
    public function get_frob() {
        if ($this->used == 'web') {
            $frob = isset($_GET['frob']) ? $_GET['frob'] : false;
            if (!$frob) {
                $this->auth();
                return;
            }
            return $frob;
        } else {
            // TODO: 补充两外2个用途
        }
    }
    public function get_token($frob) {
        $method = 'yupoo.auth.getToken';
        $result = $this->api_call($method, array('frob' => $frob));
        if ($result['stat'] == 'ok') {
            $this->token = $result['auth']['token'];
        } else {    
            $this->error_code = $result['err']['code'];
            $this->error_msg = $result['err']['msg'];
            $this->token = false;
        }
        return $this->token;
    }
    public function upload($photo, $args) {
        $args = array_merge(array("api_key" => $this->api_key, "format" => 'json'), $args);
        ksort($args);
        $auth_sig = '';
        foreach ($args as $key => $data) {
            $auth_sig .= $key . $data;
        }
        if (!empty($this->secret)) {
            $api_sig = md5($this->secret . $auth_sig);
            $args["api_sig"] = $api_sig; 
        }
        if (is_file($photo)) {
            $args['photo'] = "@$photo";
        }
        $ch = curl_init(YupooAPI::$UPLOAD_URL);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $args);
        curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.0.7) Gecko/2009021906 Firefox/3.0.7");
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_COOKIE, "");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        $content = curl_exec($ch);
        curl_close($ch);
        $res_data = json_decode($content, true);
        return $res_data;
    }
    public function get_size($args, $size = 'medium') {
        $method = "yupoo.photos.getSizes";
        $result = $this->api_call($method, $args);
        if ($result['stat'] == 'ok') {
            $sizes = $result['sizes'];
            foreach ($sizes as $ss) {
                if ( strtolower($ss['label'])  == 'medium') {
                    $default_img = $ss;
                }
                if ( strtolower($ss['label'])  == strtolower($size)) {
                    return $ss;
                }
            }
            return $default_img;
        } else {
            $this->error_code = $result['err']['code'];
            $this->error_msg = $result['err']['msg'];
        }
        return false;
    }
    public function get_photo_url($photo, $size= 'm') {
    }   
    function people_getUploadStatus()
    {
        /* Requires Authentication */
        $this->request("yupoo.people.getUploadStatus");
        return $this->parsed_response ? $this->parsed_response['rsp']['user'] : false;
    }
    function photos_addTags ($photo_id, $tags)
    {
        $this->request("yupoo.photos.addTags", array("photo_id"=>$photo_id, "tags"=>$tags), TRUE);
        return $this->parsed_response ? true : false;
    }
    function photos_delete($photo_id)
    {
        $this->request("yupoo.photos.delete", array("photo_id"=>$photo_id), TRUE);
        return $this->parsed_response ? true : false;
    }
    public function get_error_msg() {
        return $this->error_msg;
    }
    public function get_error_code() {
        return $this->error_code;
    }
}