API Endpoints #
Common #
GET v1/token #
https://tw.supplier.yahoo.com/api/spa/v1/token
取得X-YahooWSSID-Authorization
HTTP Request Header,用做後續其他 Endpoint 認證使用。
此 Token 為根據 Cookie 所計算,使用者端可以 Cookie Value 暫存此 Token 的值,不需每個 Request 都重覆計算。
Output
- HTTP Status Code: 200 (OK)
- Object: Token
Error
HTTP Status Code | Error Code | Description |
---|---|---|
400 | Invalid input | |
400 | 40000005 | Contains illegal character(s) |
401 | 40100001 | Missing or bad authentication |
401 | 40100002 | Invalid cookie |
401 | 40100003 | Missing or bad authentication |
401 | 40100004 | Check wssid failed |
401 | 40100005 | Issue wssid failed |
401 | 40100006 | Cookie has expired |
500 | 50000006 | Internal server error |
Example
- Get token by cookie
GET /api/spa/v1/token
Content-Type: application/json; charset=utf-8
Cookie: _sp=<the cookie fetched by following SignIn section>
HTTP/1.1 200 OK
{
"wssid": "ABCDEFabcdef0123456789._-~"
}
POST v1/signIn #
https://tw.supplier.yahoo.com/api/spa/v1/signIn
使用 SCM API Key 取得 Cookie。
Payload: String
Output
- HTTP Status: 204 (No Content)
Error
HTTP Status Code | Error Code | Description |
---|---|---|
400 | 40000005 | Contains illegal character(s) |
401 | 40100001 | Missing or bad authentication |
401 | 40100002 | Invalid cookie |
401 | 40100003 | Missing or bad authentication |
401 | 40100004 | Check wssid failed |
401 | 40100006 | Cookie has expired |
500 | 50000006 | Internal server error |
Example
- Get token by cookie
POST /api/spa/v1/signIn
api-token: <token>
api-keyversion: <version>
api-timestamp: <timestamp>
api-signature: <signature>
<ciphertext>
欄位說明
Sign In 至本 API 的 HTTP request 需包含 4 個 header 與 Credential 密文,分別說明如下
Data |
Location | 說明 |
---|---|---|
api-token | Header | 即為 API Key 查詢頁提供之 Token |
api-keyversion | Header | 即為 API Key 查詢頁提供之 Version |
api-timestamp | Header | 為 Request 發送當下的 Unix Timestamp,例如 1548225833,此 Timestamp 的有效期限為 90 秒 |
api-signature | Header | Cookie 密文的簽章,由 Timestamp, Token, SaltKey 與密文計算而成,可參考下方範例程式 若計算出的簽章為大寫英文時,需將簽章轉換為小寫英文。例如 AB12CD 需轉換為 ab12cd |
Credential | Body | 加密後的 Cookie,可參考下方範例程式 |
HTTP/1.1 204 No Content
Set-Cookie: <cookie>
Sign in 完成後會由 Response Set-Cookie Header 回傳 _sp
Cookie,其效期為 6 小時。存其本 API 其他各 Endpoint 都需於 Header 加上此 Cookie 欄位。
Java 範例程式
import com.github.longhorn.fastball.time.Clock;
import com.yahoo.parsec.clients.ParsecAsyncHttpRequest;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.json.JSONObject;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.core.NewCookie;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
public class Sample {
public ParsecAsyncHttpRequest signIn() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException {
// Required data
String timestamp = Clock.fromNow().getEpochSecondString();
String token = "Supplier_1234";
int supplierId = 1234;
String secretKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef01234567890=";
String iv = "ABCDEFabcdef01234567890=";
String saltKey = "ABCDEFGHIJKabcdefghij01234567890";
String version = "1";
// Credential Plaintext
JSONObject cookie = new JSONObject();
cookie.put("supplierId", supplierId);
String plaintext = cookie.toString();
// Credential Ciphertext
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.decodeBase64(secretKey), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.decodeBase64(iv));
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
String ciphertext = Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8.name())));
// Credential Signature
String signatureSource = String.format("%s%s%s%s", timestamp, token, saltKey, ciphertext);
SecretKey signatureSecretKey = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8.name()), "HmacSHA512");
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(signatureSecretKey);
byte[] rawHmac = mac.doFinal(signatureSource.getBytes(StandardCharsets.UTF_8.name()));
byte[] hexBytes = new Hex().encode(rawHmac);
String signature = new String(hexBytes, StandardCharsets.ISO_8859_1.name());
return new ParsecAsyncHttpRequest.Builder()
.setUrl("https://tw.supplier.yahoo.com:443/api/spa/v1/signIn")
.setMethod(HttpMethod.POST)
.setHeader("api-token", token)
.setHeader("api-keyversion", version)
.setHeader("api-timestamp", timestamp)
.setHeader("api-signature", signature)
.setBody(ciphertext)
.build();
}
}
PHP 範例程式
<?php
class AES
{//{{{
const OPENSSL_CIPHER_NAME = "aes-256-cbc";
const CIPHER_KEY_LEN = 32;
private $_key;
private $_iv;
public function __construct($key, $iv)
{
$base64decodedKey = base64_decode($key);
if (strlen($base64decodedKey) < AES::CIPHER_KEY_LEN) {
$this->_key = str_pad($base64decodedKey, AES::CIPHER_KEY_LEN, "0");
} else if (strlen($base64decodedKey) > AES::CIPHER_KEY_LEN) {
$this->_key = substr($base64decodedKey, 0, AES::CIPHER_KEY_LEN);
} else {
$this->_key = $base64decodedKey;
}
$this->_iv = base64_decode($iv);
}
public function encrypt($data)
{
return base64_encode(openssl_encrypt($data, AES::OPENSSL_CIPHER_NAME, $this->_key, OPENSSL_RAW_DATA, $this->_iv));
}
}//}}}
class HMac
{//{{{
private $_secretKey;
public function __construct($secretKey)
{
$this->_secretKey = $secretKey;
}
public function sha512($data)
{
return hash_hmac("sha512", $data, $this->_secretKey);
}
}//}}}
class Sample
{
private $shareSecretKey = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef01234567890=';
private $shareSecretIv = 'ABCDEFabcdef01234567890=';
private $saltKey = 'ABCDEFGHIJKabcdefghij01234567890';
private $apiVersion = '1';
private $token = 'Supplier_1234';
private function getCookie($serviceUrl, $messageBody)
{
$timestamp = time();
$aes = new AES($this->shareSecretKey, $this->shareSecretIv);
$cipherText = $aes->encrypt($messageBody);
$hasher = new HMac($this->shareSecretKey);
$signature = $hasher->sha512(sprintf("%s%s%s%s", $timestamp, $this->token, $this->saltKey, $cipherText));
$headers = [
"Content-Type: application/json; charset=utf-8",
"api-token: $this->token",
"api-signature: $signature",
"api-timestamp: $timestamp",
"api-keyversion: $this->apiVersion",
];
$httpRequest = curl_init();
curl_setopt($httpRequest, CURLOPT_URL, $serviceUrl);
curl_setopt($httpRequest, CURLOPT_POSTFIELDS, $cipherText);
curl_setopt($httpRequest, CURLOPT_RETURNTRANSFER, true);
curl_setopt($httpRequest, CURLOPT_HTTPHEADER, $headers);
curl_setopt($httpRequest, CURLOPT_POST, true);
curl_setopt($httpRequest, CURLOPT_HEADER, true);
$response = curl_exec($httpRequest);
$responseHeader = curl_getinfo($httpRequest);
curl_close($httpRequest);
if ($responseHeader['http_code'] == 204) {
preg_match_all('/^Set-Cookie:\s(?\'cookie\'_sp=.+)$/mi', $response, $matches);
return trim($matches['cookie'][0]);
} else {
throw new Exception("Failed to obtain the SCM cookie");
}
}
public function main()
{
$serviceUrl = "https://tw.supplier.yahoo.com:443/api/spa/v1/signIn";
$requestContent = '{"supplierId":"1234"}';
$cookie = $this->getCookie($serviceUrl, $requestContent);
echo $cookie."\n"; // contains the SCM cookie key and value
}
}
$sample = new Sample;
$sample->main();
Node JS 範例程式
const crypto = require('crypto')
// npm i node-fetch
const fetch = require('node-fetch')
// Required data
const timestamp = Math.round(Date.now() / 1000).toString()
const supplierId = 1234
const token = `Supplier_1234`
const secretKey = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef01234567890='
const iv = 'ABCDEFabcdef01234567890='
const saltKey = 'ABCDEFGHIJKabcdefghij01234567890'
const version = '1'
const getSetCookie = async () => {
// Credential Plaintext
const plaintext = JSON.stringify({ supplierId })
// Credential Ciphertext
const cipher = crypto.createCipheriv(
'aes-256-cbc',
Buffer.from(secretKey, 'base64'),
Buffer.from(iv, 'base64')
)
const cipherText = `${cipher.update(plaintext, 'utf8', 'base64')}${cipher.final('base64')}`
// Credential Signature
const hmac = crypto.createHmac('sha512', secretKey)
const signatureSource = `${timestamp}${token}${saltKey}${cipherText}`
const signature = hmac.update(signatureSource).digest('hex')
const url = 'https://tw.supplier.yahoo.com/api/spa/v1/signIn'
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'api-token': token,
'api-signature': signature,
'api-timestamp': timestamp,
'api-keyversion': version,
'api-supplierid': supplierId
}
const resp = await fetch(url, { method: 'POST', headers, body: cipherText })
.then(res => ({
status: res.status,
statusText: res.statusText,
setCookie: res.headers.get('set-cookie')
}))
.catch(e => e)
console.log('response: ', resp)
}
getSetCookie()
C# 範例程式
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
internal class Program
{
private static void Main(string[] args)
{
// Required data
var timestamp = GetTimestamp().ToString();
var supplierId = 1234;
var apiTtoken = "Supplier_1234";
var apiKeyValue = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef01234567890=";
var apiKeyIV = "ABCDEFabcdef01234567890=";
var saltKey = "ABCDEFGHIJKabcdefghij01234567890";
var apiVersion = "1";
// Credential Plaintext
var text = new { supplierId = supplierId };
var plaintext = JsonConvert.SerializeObject(text);
// Credential Ciphertext
var aes = Aes.Create();
aes.Key = Convert.FromBase64String(apiKeyValue);
aes.IV = Convert.FromBase64String(apiKeyIV);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var cipher = aes.CreateEncryptor(aes.Key, aes.IV);
var ciphertextBytes = cipher.TransformFinalBlock(Encoding.UTF8.GetBytes(plaintext), 0, plaintext.Length);
var ciphertext = Convert.ToBase64String(ciphertextBytes);
aes.Clear();
// Credential Signature
var encoder = new UTF8Encoding();
var keyBytes = encoder.GetBytes(apiKeyValue);
var hMAC = new HMACSHA512(keyBytes);
var hash = hMAC.ComputeHash(encoder.GetBytes($"{timestamp}{apiTtoken}{saltKey}{ciphertext}"));
var signature = BitConverter.ToString(hash).Replace("-", string.Empty).ToLower();
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("api-token", apiTtoken);
client.DefaultRequestHeaders.Add("api-signature", signature);
client.DefaultRequestHeaders.Add("api-timestamp", timestamp);
client.DefaultRequestHeaders.Add("api-keyversion", apiVersion);
client.Timeout = new TimeSpan(0, 2, 0);
string apiUrl = "https://tw.supplier.yahoo.com/api/spa/v1/signIn";
var content = new StringContent(ciphertext, Encoding.UTF8, "application/json");
var response = client.PostAsync(apiUrl, content).Result;
}
}
private static int GetTimestamp()
{
var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return (int)(DateTime.Now.ToUniversalTime() - unixEpoch).TotalSeconds;
}
}