Tích hợp Agent & LLM — FastOG
Hướng dẫn hoàn chỉnh, khép kín để tích hợp FastOG từ bất kỳ ngôn ngữ phía máy chủ nào — được viết cho cả nhà phát triển con người lẫn tác nhân mã hóa AI. Toàn bộ bề mặt máy khách chỉ là một hàm: signedUrl(params, key, secret) → một URL <meta property="og:image"> sẵn sàng để nhúng.
Tải kỹ năng
Mang toàn bộ kỹ năng theo bạn — đặt cả hai tệp vào thư mục agents/skills/fastog-integration/ của bạn để tải trong Claude Code, Codex hoặc bất kỳ tác nhân nào:
SKILL.md—curl -O https://fastog.com/skill/SKILL.mdimplementations.md—curl -O https://fastog.com/skill/implementations.md
Bạn muốn một tệp kết hợp? Trang này chính là kỹ năng (hợp đồng + triển khai nội tuyến): curl -O https://fastog.com/docs-files/agent-integration.md
Hợp đồng (hợp đồng duy nhất)
URL = https://fastog.com/api/v1/og?<content>&key=<apiKey>&s=<signature>
canonical = every query param except `s` (INCLUDING `key`), keys sorted,
array values sorted, each pair enc(key)=enc(value) RFC-3986,
joined with "&"
message = "GET/api/v1/og" + "\n" + canonical
signature = hex(HMAC-SHA256(message, hmac_secret))
Bất biến (vi phạm bất kỳ điều nào → 401)
- Mã hóa RFC-3986 — mã hóa phần trăm mọi thứ trừ
A-Za-z0-9-._~; dấu cách →%20, không bao giờ+. PHPrawurlencode; JSencodeURIComponent+ mã hóa lại!'()*; Pythonquote(safe='-._~'); Ruby/Go bộ mã hóa byte tùy chỉnh. keyđược ký — nó là một phần của chuỗi truy vấn chuẩn, vì vậy chữ ký được ràng buộc với khóa.- Mảng cần khóa có dấu ngoặc trong URL — ký chuỗi chuẩn với khóa thường (
features=…&features=…), nhưng URL phải dùngfeatures[0]=…hoặcfeatures[]=…. Backend là PHP: các khóa lặp lại không có dấu ngoặc (features=a&features=b) giải mã thành vô hướng (giá trị cuối thắng) → chuỗi chuẩn không khớp → 401. - Không có dấu thời gian — định địa chỉ theo nội dung. Tham số giống nhau → URL giống nhau → có thể lưu cache vĩnh viễn; bất kỳ thay đổi nào → 401 → không render, không tiêu tốn hạn mức.
Tham số dành riêng: s (chữ ký — loại trừ khỏi chuỗi chuẩn), key (bao gồm), t (dành riêng — không bao giờ dùng).
Bảo mật (bắt buộc tuyệt đối)
hmac_secretlà riêng tư — chỉ dùng phía máy chủ. Không bao giờ đặt trong trình duyệt, không bao giờ đặt trong script CDN công khai. Việc ký phía máy khách/trình duyệt không bao giờ hoạt động được.keylà công khai (xuất hiện trong mọi URL) — điều này là bình thường và an toàn, vì việc giả mạo cần khóa bí mật.- Chỉ dùng GET (trình thu thập không bao giờ POST). Ký trên máy chủ tại thời điểm yêu cầu; URL đã ký chính là thứ bạn nhúng.
Ví dụ minh họa
params = {template:"blog", title:"Hello World", features:["Free shipping","30-day returns"], key:"sk_test123"}
secret = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
canonical = features=30-day%20returns&features=Free%20shipping&key=sk_test123&template=blog&title=Hello%20World
signature = a79232b15b45002aee76174f542e0a661b37cb1259df01e263c952ca4052a54d
url = https://fastog.com/api/v1/og?template=blog&title=Hello%20World&features%5B0%5D=Free%20shipping&features%5B1%5D=30-day%20returns&key=sk_test123&s=a79232b15b45002aee76174f542e0a661b37cb1259df01e263c952ca4052a54d
Trình ký một tệp (không phụ thuộc, không cần trình quản lý gói)
Chưa có gói npm i/pip install nào — đây là các trình ký một tệp: mỗi trình tải về dưới dạng một tệp duy nhất và chỉ tạo ra URL hình OG đã ký (toàn bộ logic HMAC + RFC-3986, không có runtime). Tải về và ký trên máy chủ của khách hàng:
| Ngôn ngữ/Khung | Tệp | Một dòng |
|---|---|---|
| Node.js (CJS) / Express | https://fastog.com/sdk/node.js · /sdk/express/fastog.js |
ogImageUrl(params, { key, secret }) |
| ESM (SvelteKit / Next / Astro / Vue-Nuxt) | /sdk/svelte/fastog.js · /sdk/next/fastog.js · /sdk/astro/fastog.js · /sdk/vue/fastog.js |
ogImageUrl(params, { key, secret }) |
| PHP | https://fastog.com/sdk/php/fastog.php.txt |
FastOg::signedUrl($params, $key, $secret) |
| Ruby / Rails | https://fastog.com/sdk/ruby/fastog.rb |
FastOg.signed_url(params, key, secret) |
| Python | https://fastog.com/sdk/python/fastog.py |
fastog.signed_url(params, key, secret) |
| Go | https://fastog.com/sdk/go/fastog.go |
fastog.SignedURL(params, key, secret) |
Nhúng kết quả:
<meta property="og:image" content="https://fastog.com/api/v1/og?…&key=…&s=…" />
Vector vàng (tự xác minh MỌI triển khai)
params = {template:"blog", title:"Hello World", features:["Free shipping","30-day returns"], key:"sk_test123"}
secret = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
canonical = features=30-day%20returns&features=Free%20shipping&key=sk_test123&template=blog&title=Hello%20World
signature = a79232b15b45002aee76174f542e0a661b37cb1259df01e263c952ca4052a54d
Một triển khai được coi là chính xác khi và chỉ khi nó tạo ra giá trị chuẩn và chữ ký đó cho các đầu vào tương ứng — và một tham số/khóa khác phải làm thay đổi chữ ký. Vector vàng được tạo ra bởi chính máy chủ FastOG, vì vậy nó là nguồn chân lý.
Triển khai tham khảo (không dùng SDK / bất kỳ ngôn ngữ nào)
JavaScript (Node.js, CommonJS)
const crypto = require("crypto");
const enc = (s) =>
encodeURIComponent(String(s)).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
function canonicalize(params, apiKey) {
const all = { ...params, key: apiKey };
const pairs = [];
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? [...v].sort() : [v]) {
pairs.push(`${enc(k)}=${enc(vv)}`);
}
}
return pairs.sort().join("&");
}
function sign(params, apiKey, secret) {
return crypto
.createHmac("sha256", secret)
.update(`GET/api/v1/og\n${canonicalize(params, apiKey)}`)
.digest("hex");
}
function signedUrl(
params,
apiKey,
secret,
baseUrl = "https://fastog.com/api/v1/og",
) {
const url = new URL(baseUrl);
for (const [k, v] of Object.entries({ ...params, key: apiKey })) {
const isArray = Array.isArray(v);
for (const vv of isArray ? v : [v]) {
url.searchParams.append(isArray ? `${k}[]` : k, vv);
}
}
url.searchParams.set("s", sign(params, apiKey, secret));
return url.toString();
}
module.exports = { signedUrl, sign, canonicalize };
PHP
function fastog_canonicalize(array $params, string $apiKey): string
{
$pairs = [];
foreach (array_merge($params, ['key' => $apiKey]) as $k => $v) {
$values = is_array($v) ? $v : [$v];
sort($values, SORT_STRING);
foreach ($values as $vv) {
$pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $vv);
}
}
sort($pairs, SORT_STRING);
return implode('&', $pairs);
}
function fastog_sign(array $params, string $apiKey, string $secret): string
{
return hash_hmac('sha256', 'GET/api/v1/og' . "\n" . fastog_canonicalize($params, $apiKey), $secret);
}
function fastog_signed_url(array $params, string $apiKey, string $secret, string $baseUrl = 'https://fastog.com/api/v1/og'): string
{
$all = array_merge($params, ['key' => $apiKey, 's' => fastog_sign($params, $apiKey, $secret)]);
return $baseUrl . '?' . http_build_query($all, '', '&', PHP_QUERY_RFC3986);
}
Python
import hashlib, hmac
from urllib.parse import quote
_enc = lambda s: quote(str(s), safe="-._~")
def canonicalize(params, api_key):
pairs = []
for k, v in {**params, "key": api_key}.items():
for vv in sorted(v) if isinstance(v, list) else [v]:
pairs.append(f"{_enc(k)}={_enc(vv)}")
return "&".join(sorted(pairs))
def sign(params, api_key, secret):
return hmac.new(secret.encode(), f"GET/api/v1/og\n{canonicalize(params, api_key)}".encode(), hashlib.sha256).hexdigest()
def signed_url(params, api_key, secret, base_url="https://fastog.com/api/v1/og"):
all_p = {**params, "key": api_key, "s": sign(params, api_key, secret)}
pairs = []
for k, v in all_p.items():
if isinstance(v, list):
for i, vv in enumerate(v):
pairs.append(f"{_enc(f'{k}[{i}]')}={_enc(vv)}")
else:
pairs.append(f"{_enc(k)}={_enc(v)}")
return f"{base_url}?{'&'.join(pairs)}"
Ruby
require "openssl"
def enc(s)
s.to_s.gsub(/[^A-Za-z0-9\-._~]/) { |c| c.bytes.map { |b| "%%%02X" % b }.join }
end
def canonicalize(params, api_key)
pairs = params.merge("key" => api_key).flat_map do |k, v|
values = v.is_a?(Array) ? v.sort : [v]
values.map { |vv| "#{enc(k)}=#{enc(vv)}" }
end
pairs.sort.join("&")
end
def sign(params, api_key, secret)
OpenSSL::HMAC.hexdigest("SHA256", secret, "GET/api/v1/og\n#{canonicalize(params, api_key)}")
end
def signed_url(params, api_key, secret, base_url = "https://fastog.com/api/v1/og")
all = params.merge("key" => api_key, "s" => sign(params, api_key, secret))
pairs = all.flat_map do |k, v|
if v.is_a?(Array)
v.each_with_index.map { |vv, i| "#{enc("#{k}[#{i}]")}=#{enc(vv)}" }
else
["#{enc(k)}=#{enc(v)}"]
end
end
"#{base_url}?#{pairs.join("&")}"
end
Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
)
func enc(s string) string {
const hex = "0123456789ABCDEF"
var b strings.Builder
for i := 0; i < len(s); i++ {
c := s[i]
if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '~' {
b.WriteByte(c)
} else {
b.WriteByte('%')
b.WriteByte(hex[c>>4])
b.WriteByte(hex[c&0x0f])
}
}
return b.String()
}
func canonicalize(params map[string]any, apiKey string) string {
all := map[string]any{}
for k, v := range params {
all[k] = v
}
all["key"] = apiKey
keys := make([]string, 0, len(all))
for k := range all {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
vals := []string{fmt.Sprint(all[k])}
if arr, ok := all[k].([]string); ok {
vals = arr
sort.Strings(vals)
}
for _, v := range vals {
pairs = append(pairs, enc(k)+"="+enc(v))
}
}
sort.Strings(pairs)
return strings.Join(pairs, "&")
}
func sign(params map[string]any, apiKey, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte("GET/api/v1/og\n" + canonicalize(params, apiKey)))
return hex.EncodeToString(mac.Sum(nil))
}
func signedURL(params map[string]any, apiKey, secret, baseURL string) string {
all := map[string]any{}
for k, v := range params {
all[k] = v
}
all["key"] = apiKey
all["s"] = sign(params, apiKey, secret)
keys := make([]string, 0, len(all))
for k := range all {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
if arr, ok := all[k].([]string); ok {
for i, v := range arr {
pairs = append(pairs, enc(k+"["+fmt.Sprint(i)+"]")+"="+enc(v))
}
} else {
pairs = append(pairs, enc(k)+"="+enc(fmt.Sprint(all[k])))
}
}
return baseURL + "?" + strings.Join(pairs, "&")
}
Gỡ lỗi lỗi 401
| Mã | Ý nghĩa | Cách khắc phục |
|---|---|---|
missing_signature |
URL thiếu key hoặc s |
tạo lại bằng signer; xác minh cả hai tham số đều có mặt |
invalid_signature |
canonical không khớp hoặc sai secret | ký lại sau BẤT KỲ thay đổi nội dung nào; xác nhận cùng một hmac_secret ở cả hai phía; kiểm tra space là %20 (không phải +); kiểm tra mảng dùng khóa trong ngoặc vuông trong URL; xác nhận key nằm trong canonical |