Chủ đề

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.md
  • implementations.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)

  1. 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ờ +. PHP rawurlencode; JS encodeURIComponent + mã hóa lại !'()*; Python quote(safe='-._~'); Ruby/Go bộ mã hóa byte tùy chỉnh.
  2. 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.
  3. 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ùng features[0]=… hoặc features[]=…. 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.
  4. 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_secret là 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.
  • key là 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ả:

html
<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)

js
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

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

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

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

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

Dùng thử miễn phí — không cần đăng ký

Xem trước và kiểm tra hình ảnh OG động trong vài giây.

Mở Trình kiểm tra hình ảnh OG miễn phí

A BeCaSimulations site