生成 JWT 访问令牌
POST /api/auth/token
POST https://api.fypay.me/api/auth/token使用 X.509 客户端证书 + 凭证(clientId/clientSecret)进行身份验证并获取 JWT 令牌。令牌有效期由 expires_in 字段(单位:秒)给出:请根据该值在过期前续期,不要假定固定时长。
身份验证(mTLS)
此接口要求 mTLS:FyPay 签发的客户端证书在 TLS 握手中出示,而不是放在 HTTP 请求头中。请使用 HTTP 库自带的客户端证书机制(例如 curl --cert client.crt --key client.key)。
- 证书为 PEM 格式、RSA 2048,有效期 2 年,并与您的账户绑定。
- 仅此接口要求 mTLS。其他接口使用
Authorization: Bearer <token>,无需证书。
不要通过 HTTP 请求头发送证书:该方式不被接受。如果握手中没有证书,API 将返回 400 和 PUB_CERT_HEADER_MISSING。
请求体
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
clientId | string (UUID v4) | 是 | 在控制台(https://banking.fypay.me)的 设置 → 凭证 中生成的 Client ID |
clientSecret | string | 是 | 与 clientId 一同生成的 Client secret,在控制台中仅显示一次。不能为空 |
{
"clientId": "6967c0a0-3566-48d0-8c13-717038b52279",
"clientSecret": "YOUR_CLIENT_SECRET"
}cURL 示例
curl -X POST https://api.fypay.me/api/auth/token \
--cert ./client.crt \
--key ./client.key \
-H "Content-Type: application/json" \
-d '{
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET"
}'代码示例
const https = require('https');
const fs = require('fs');
const axios = require('axios');
const httpsAgent = new https.Agent({
cert: fs.readFileSync('./client.crt'),
key: fs.readFileSync('./client.key'),
});
const response = await axios.post(
'https://api.fypay.me/api/auth/token',
{
clientId: process.env.FYPAY_CLIENT_ID,
clientSecret: process.env.FYPAY_CLIENT_SECRET,
},
{ httpsAgent },
);
console.log('Token:', response.data.access_token);
console.log('Expires in:', response.data.expires_in, 'seconds');import os
import requests
response = requests.post(
'https://api.fypay.me/api/auth/token',
json={
'clientId': os.environ['FYPAY_CLIENT_ID'],
'clientSecret': os.environ['FYPAY_CLIENT_SECRET'],
},
cert=('client.crt', 'client.key'),
)
response.raise_for_status()
data = response.json()
print(f"Token: {data['access_token']}")
print(f"Expires in: {data['expires_in']} seconds")<?php
$ch = curl_init('https://api.fypay.me/api/auth/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSLCERT => __DIR__ . '/client.crt',
CURLOPT_SSLKEY => __DIR__ . '/client.key',
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'clientId' => getenv('FYPAY_CLIENT_ID'),
'clientSecret' => getenv('FYPAY_CLIENT_SECRET'),
]),
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
echo "Token: " . $data['access_token'] . "\n";
echo "Expires in: " . $data['expires_in'] . " seconds\n";using System.Net.Http.Json;
using System.Security.Cryptography.X509Certificates;
var certificate = X509Certificate2.CreateFromPemFile("client.crt", "client.key");
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
using var client = new HttpClient(handler);
var response = await client.PostAsJsonAsync(
"https://api.fypay.me/api/auth/token",
new
{
clientId = Environment.GetEnvironmentVariable("FYPAY_CLIENT_ID"),
clientSecret = Environment.GetEnvironmentVariable("FYPAY_CLIENT_SECRET"),
});
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
panic(err)
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
},
}
body, _ := json.Marshal(map[string]string{
"clientId": os.Getenv("FYPAY_CLIENT_ID"),
"clientSecret": os.Getenv("FYPAY_CLIENT_SECRET"),
})
resp, err := client.Post("https://api.fypay.me/api/auth/token", "application/json", bytes.NewReader(body))
if err != nil {
panic(err)
}
defer resp.Body.Close()
var data map[string]any
json.NewDecoder(resp.Body).Decode(&data)
fmt.Println("Token:", data["access_token"])
fmt.Println("Expires in:", data["expires_in"], "seconds")
}import java.io.FileInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
char[] password = System.getenv("FYPAY_KEYSTORE_PASSWORD").toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream in = new FileInputStream("client.p12")) {
keyStore.load(in, password);
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, null);
String body = String.format(
"{\"clientId\":\"%s\",\"clientSecret\":\"%s\"}",
System.getenv("FYPAY_CLIENT_ID"),
System.getenv("FYPAY_CLIENT_SECRET")
);
HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.fypay.me/api/auth/token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());FyPay 以 PEM 格式(client.crt + client.key)提供证书。如果您的平台需要 .pfx/.p12 文件(Java 和部分 Windows/.NET 环境常见),请在本地转换:
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12转换在您自己的机器上进行;FyPay 不提供 PFX 文件。
响应 (201)
| 字段 | 类型 | 描述 |
|---|---|---|
access_token | string | 用于 Authorization: Bearer <token> 请求头的 JWT 令牌 |
token_type | string | 固定为 Bearer |
expires_in | number | 令牌有效期(秒)。请根据该值在过期前续期 |
scope | string | 令牌作用域 |
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 600,
"scope": "email profile"
}错误
| 状态码 | 错误码 | 描述 |
|---|---|---|
| 400 | PUB_CERT_HEADER_MISSING | TLS 握手中未出示客户端证书 |
| 400 | PUB_CERT_MALFORMED_PEM | 证书的 PEM 格式无效 |
| 400 | PUB_REQUEST_BODY_INVALID | 请求体无效(例如 clientId 不是 UUID v4、clientSecret 为空、包含多余字段)。出错字段列在 details.violations 中 |
| 401 | PUB_INVALID_CREDENTIALS | clientId/clientSecret 无效 |
| 401 | PUB_CERT_NOT_VERIFIED | 连接中的证书无法验证 |
| 401 | PUB_CERT_NOT_REGISTERED | 证书未注册 |
| 401 | PUB_CERT_EXPIRED | 证书已过期 |
| 401 | PUB_CERT_NOT_YET_VALID | 证书尚未进入有效期 |
| 403 | PUB_CERT_NOT_AUTHORIZED_FOR_ACCOUNT | 证书未绑定到该凭证所属账户 |
| 502 | PUB_AUTH_UPSTREAM_ERROR | 认证服务出现意外错误 |
| 503 | PUB_AUTH_UPSTREAM_UNAVAILABLE | 认证服务暂时不可用,请重试 |
{
"statusCode": 401,
"timestamp": "2026-04-17T15:24:28.809Z",
"path": "/api/auth/token",
"method": "POST",
"code": "PUB_INVALID_CREDENTIALS",
"message": "Invalid credentials provided",
"userMessage": "Invalid credentials.",
"errorId": "75d7999d6d7656b737e83435ced86ec6"
}有关完整的身份验证指南和令牌续期模式,请参阅 身份验证指南。