통합 예제
Owning.pro와 가장 일반적인 통합 패턴을 다루는 세 가지 엔드투엔드 예제. 각 예제는 curl, TypeScript, Python 구현을 포함합니다.
| # | 예제 | 인증 | 도구 / 엔드포인트 |
|---|---|---|---|
| 1 | Search & Evaluate | None (public) | GET /api/listings, GET /api/listings/:id.md |
| 2 | Publish a Listing | API key (write) | POST /api/api-keys, POST /api/upload, POST /api/listings, POST /api/listings/:id/publish |
| 3 | Use MCP Server | API key (optional) | search_listings, get_listing_markdown, create_listing, publish_listing |
| 4 | Track Analytics Events | None (public) | POST /api/track |
| 5 | Promote a Listing (Premium) | JWT (owner) | POST /api/listings/:id/premium, GET /api/listings/:id/premium-status *(planned)* |
예제 1: 검색 & 평가
시나리오: AI 에이전트가 특정 카테고리에서 보트를 검색하고, 가격으로 필터링하고, 최상위 결과의 Markdown 상세를 가져오고, 좋은 거래인지 평가합니다.
이 예제는 공개 읽기 엔드포인트만 사용합니다. API 키나 JWT가 필요 없습니다. 속도 제한: IP당 분당 100 요청.
흐름
1. Search listings (category=boats, max_price=200000)
→ GET /api/listings?category=boats&max_price=200000&limit=10
2. Pick the first result
3. Get the Markdown detail
→ GET /api/listings/{slug}.md
4. Parse and evaluatecurl
# Step 1: Search for boats under 200k
curl -s "https://api.owning.pro/api/listings?category=boats&max_price=200000&limit=10" \
| jq '.results[0].slug'
# → "lagoon-400-s2-JP12QW"
# Step 2: Get the Markdown detail
curl -s "https://api.owning.pro/api/listings/lagoon-400-s2-JP12QW.md"
# → Returns Markdown with YAML frontmatter + structured sectionsTypeScript
// search-and-evaluate.ts
const API_BASE = "https://api.owning.pro";
async function searchAndEvaluate() {
// Step 1: Search for boats under 200k
const searchUrl = new URL(`${API_BASE}/api/listings`);
searchUrl.searchParams.set("category", "boats");
searchUrl.searchParams.set("max_price", "200000");
searchUrl.searchParams.set("limit", "10");
const searchRes = await fetch(searchUrl);
const searchData = await searchRes.json();
console.log(`Found ${searchData.pagination.total} listings`);
if (searchData.results.length === 0) {
console.log("No results found");
return;
}
// Step 2: Pick the first result
const topListing = searchData.results[0];
console.log(`Top result: ${topListing.title} — ${topListing.price.amount} ${topListing.price.currency}`);
// Step 3: Get the Markdown detail
const mdRes = await fetch(`${API_BASE}/api/listings/${topListing.slug}.md`);
const markdown = await mdRes.text();
// Step 4: Evaluate
console.log("\n--- Listing Detail (Markdown) ---\n");
console.log(markdown);
// Parse key info from the Markdown for evaluation
const priceMatch = markdown.match(/price:\s*(\d+)/);
const yearMatch = markdown.match(/Year.*?(\d{4})/);
const lengthMatch = markdown.match(/Length.*?([\d.]+)\s*m/);
const evaluation = {
title: topListing.title,
price: priceMatch ? parseInt(priceMatch[1]) : null,
year: yearMatch ? parseInt(yearMatch[1]) : null,
length: lengthMatch ? parseFloat(lengthMatch[1]) : null,
pricePerMeter: null as number | null,
};
if (evaluation.price && evaluation.length) {
evaluation.pricePerMeter = Math.round(evaluation.price / evaluation.length);
}
console.log("\n--- Evaluation ---");
console.log(JSON.stringify(evaluation, null, 2));
}
searchAndEvaluate().catch(console.error);Python
# search_and_evaluate.py
import requests
import re
import json
API_BASE = "https://api.owning.pro"
def search_and_evaluate():
# Step 1: Search for boats under 200k
params = {
"category": "boats",
"max_price": "200000",
"limit": "10",
}
resp = requests.get(f"{API_BASE}/api/listings", params=params)
data = resp.json()
print(f"Found {data['pagination']['total']} listings")
if not data["results"]:
print("No results found")
return
# Step 2: Pick the first result
top = data["results"][0]
print(f"Top result: {top['title']} — {top['price']['amount']} {top['price']['currency']}")
# Step 3: Get the Markdown detail
md_resp = requests.get(f"{API_BASE}/api/listings/{top['slug']}.md")
markdown = md_resp.text
# Step 4: Evaluate
print("\n--- Listing Detail (Markdown) ---\n")
print(markdown)
# Parse key info
price_match = re.search(r"price:\s*(\d+)", markdown)
year_match = re.search(r"Year.*?(\d{4})", markdown)
length_match = re.search(r"Length.*?([\d.]+)\s*m", markdown)
evaluation = {
"title": top["title"],
"price": int(price_match.group(1)) if price_match else None,
"year": int(year_match.group(1)) if year_match else None,
"length": float(length_match.group(1)) if length_match else None,
"price_per_meter": None,
}
if evaluation["price"] and evaluation["length"]:
evaluation["price_per_meter"] = round(evaluation["price"] / evaluation["length"])
print("\n--- Evaluation ---")
print(json.dumps(evaluation, indent=2))
if __name__ == "__main__":
search_and_evaluate()예제 2: 등록글 게시
시나리오: 에이전트가 계정을 가입하고, 로그인하고, 쓰기 권한이 있는 API 키를 생성하고, 이미지를 업로드하고, 등록글을 생성하고, 게시합니다. 전체 엔드투엔드 쓰기 흐름입니다.
이 예제는 API 키 생성에 JWT 토큰(가입/로그인)이 필요하고, 이후 쓰기 작업에 API 키가 필요합니다. 속도 제한: 하루 10개 등록글, 하루 50개 이미지 업로드.
흐름
1. Register an account → POST /api/auth/register
2. Login (get JWT) → POST /api/auth/login
3. Create an API key → POST /api/api-keys (JWT auth)
4. Upload an image → POST /api/upload (API key auth)
5. Create a listing → POST /api/listings (API key auth)
6. Publish the listing → POST /api/listings/:id/publish (API key auth)curl
# Step 1: Register
curl -s -X POST https://api.owning.pro/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"agent@example.com","password":"SecurePass123!","name":"AI Agent"}'
# Step 2: Login (get JWT)
JWT=$(curl -s -X POST https://api.owning.pro/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"agent@example.com","password":"SecurePass123!"}' \
| jq -r '.token')
# Step 3: Create an API key with write permission
API_KEY=$(curl -s -X POST https://api.owning.pro/api/api-keys \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT" \
-d '{"name":"agent-key","permissions":["read","write"]}' \
| jq -r '.key')
echo "API Key: $API_KEY"
# Step 4: Upload an image
IMAGE_URL=$(curl -s -X POST https://api.owning.pro/api/upload \
-H "Authorization: Bearer $API_KEY" \
-F "file=@photo.jpg" \
| jq -r '.url')
# Step 5: Create a listing
LISTING_ID=$(curl -s -X POST https://api.owning.pro/api/listings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d "{
\"title\": \"Apple iPhone 13 128GB Blue\",
\"description\": \"Apple iPhone 13 in blue, 128GB. Good condition, battery health 89%.\",
\"category_id\": \"electronics\",
\"condition\": \"good\",
\"price\": { \"amount\": 399, \"currency\": \"EUR\", \"negotiable\": true },
\"location\": { \"country\": \"ES\", \"city\": \"Madrid\", \"shipping\": true },
\"images\": [{ \"url\": \"$IMAGE_URL\" }],
\"tags\": [\"iphone\", \"apple\", \"smartphone\"]
}" \
| jq -r '.id')
echo "Listing ID: $LISTING_ID"
# Step 6: Publish the listing
curl -s -X POST "https://api.owning.pro/api/listings/$LISTING_ID/publish" \
-H "Authorization: Bearer $API_KEY"
# → { "id": "...", "status": "active", ... }TypeScript
// publish-listing.ts
const API_BASE = "https://api.owning.pro";
async function publishListing() {
// Step 1: Register
const regRes = await fetch(`${API_BASE}/api/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "agent@example.com",
password: "SecurePass123!",
name: "AI Agent",
}),
});
// Ignore 409 (already registered) — proceed to login
// Step 2: Login
const loginRes = await fetch(`${API_BASE}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "agent@example.com",
password: "SecurePass123!",
}),
});
const { token: jwt } = await loginRes.json();
// Step 3: Create an API key
const keyRes = await fetch(`${API_BASE}/api/api-keys`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${jwt}`,
},
body: JSON.stringify({
name: "agent-key",
permissions: ["read", "write"],
}),
});
const { key: apiKey } = await keyRes.json();
console.log("API Key:", apiKey);
// Step 4: Upload an image
const formData = new FormData();
const imageBuffer = await fetch("https://example.com/photo.jpg").then((r) => r.blob());
formData.append("file", imageBuffer, "photo.jpg");
const uploadRes = await fetch(`${API_BASE}/api/upload`, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: formData,
});
const { url: imageUrl } = await uploadRes.json();
console.log("Image URL:", imageUrl);
// Step 5: Create a listing
const createRes = await fetch(`${API_BASE}/api/listings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
title: "Apple iPhone 13 128GB Blue",
description: "Apple iPhone 13 in blue, 128GB. Good condition, battery health 89%.",
category_id: "electronics",
condition: "good",
price: { amount: 399, currency: "EUR", negotiable: true },
location: { country: "ES", city: "Madrid", shipping: true },
images: [{ url: imageUrl }],
tags: ["iphone", "apple", "smartphone"],
}),
});
const listing = await createRes.json();
console.log("Listing ID:", listing.id);
// Step 6: Publish
const pubRes = await fetch(`${API_BASE}/api/listings/${listing.id}/publish`, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
});
const published = await pubRes.json();
console.log("Published:", published.status); // → "active"
}
publishListing().catch(console.error);Python
# publish_listing.py
import requests
API_BASE = "https://api.owning.pro"
def publish_listing():
# Step 1: Register
requests.post(f"{API_BASE}/api/auth/register", json={
"email": "agent@example.com",
"password": "SecurePass123!",
"name": "AI Agent",
})
# Ignore 409 (already registered)
# Step 2: Login
login_resp = requests.post(f"{API_BASE}/api/auth/login", json={
"email": "agent@example.com",
"password": "SecurePass123!",
})
jwt = login_resp.json()["token"]
# Step 3: Create an API key
key_resp = requests.post(f"{API_BASE}/api/api-keys", json={
"name": "agent-key",
"permissions": ["read", "write"],
}, headers={"Authorization": f"Bearer {jwt}"})
api_key = key_resp.json()["key"]
print(f"API Key: {api_key}")
auth_headers = {"Authorization": f"Bearer {api_key}"}
# Step 4: Upload an image
with open("photo.jpg", "rb") as f:
upload_resp = requests.post(
f"{API_BASE}/api/upload",
headers=auth_headers,
files={"file": ("photo.jpg", f, "image/jpeg")},
)
image_url = upload_resp.json()["url"]
print(f"Image URL: {image_url}")
# Step 5: Create a listing
create_resp = requests.post(f"{API_BASE}/api/listings", json={
"title": "Apple iPhone 13 128GB Blue",
"description": "Apple iPhone 13 in blue, 128GB. Good condition, battery health 89%.",
"category_id": "electronics",
"condition": "good",
"price": {"amount": 399, "currency": "EUR", "negotiable": True},
"location": {"country": "ES", "city": "Madrid", "shipping": True},
"images": [{"url": image_url}],
"tags": ["iphone", "apple", "smartphone"],
}, headers={**auth_headers, "Content-Type": "application/json"})
listing = create_resp.json()
print(f"Listing ID: {listing['id']}")
# Step 6: Publish
pub_resp = requests.post(
f"{API_BASE}/api/listings/{listing['id']}/publish",
headers=auth_headers,
)
published = pub_resp.json()
print(f"Published: {published['status']}") # → "active"
if __name__ == "__main__":
publish_listing()예제 3: MCP 서버 사용
시나리오: 에이전트가 Owning MCP 서버에 연결하고, 사용 가능한 도구를 나열하고, 등록글을 검색하고, Markdown 형식으로 하나를 가져오고, 새 등록글을 생성하고, 게시합니다 — 모두 원시 HTTP 요청 대신 MCP 도구 호출을 통해.
이 예제는 예제 1과 2와 같은 목표를 달성하지만, MCP 서버의 도구 인터페이스를 통해. MCP 서버가 내부적으로 인증 헤더, URL 구성, 응답 파싱을 처리합니다.
흐름
1. Initialize MCP session → method: "initialize"
2. List available tools → method: "tools/list"
3. Search listings → tool: "search_listings"
4. Get Markdown detail → tool: "get_listing_markdown"
5. Create a listing → tool: "create_listing"
6. Publish the listing → tool: "publish_listing"curl
MCP_URL="https://mcp.owning.pro/mcp"
API_KEY="own_aBcD123eFgH456iJkL789mNoP012qRsT345uVwX678yZ"
# Step 1: Initialize
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# Step 2: List tools
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# Step 3: Search listings
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"search_listings","arguments":{
"query":"lagoon","category":"boats","limit":5
}}
}'
# Step 4: Get Markdown detail
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"get_listing_markdown","arguments":{
"id_or_slug":"lagoon-400-s2-JP12QW"
}}
}'
# Step 5: Create a listing
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"jsonrpc":"2.0","id":5,"method":"tools/call",
"params":{"name":"create_listing","arguments":{
"title":"Apple iPhone 13 128GB Blue",
"description":"Apple iPhone 13 in blue, 128GB. Good condition, battery health 89%.",
"category_id":"electronics",
"condition":"good",
"price":{"amount":399,"currency":"EUR","negotiable":true},
"location":{"country":"ES","city":"Madrid","shipping":true},
"tags":["iphone","apple","smartphone"]
}}
}'
# Step 6: Publish (use the listing ID from step 5)
curl -s -X POST "$MCP_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"jsonrpc":"2.0","id":6,"method":"tools/call",
"params":{"name":"publish_listing","arguments":{
"id_or_slug":"apple-iphone-13-128gb-blue"
}}
}'TypeScript
// use-mcp-server.ts
const MCP_URL = "https://mcp.owning.pro/mcp";
const API_KEY = process.env.OWNING_API_KEY; // own_... format
let nextId = 1;
async function callMCP(method: string, params: Record<string, unknown> = {}) {
const res = await fetch(MCP_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({
jsonrpc: "2.0",
id: nextId++,
method,
params,
}),
});
const data = await res.json();
if (data.error) throw new Error(data.error.message);
return data.result;
}
async function callTool(name: string, args: Record<string, unknown>) {
const result = await callMCP("tools/call", { name, arguments: args });
if (result.isError) {
throw new Error(result.content[0].text);
}
// Parse the text content as JSON if possible
const text = result.content[0].text;
try {
return JSON.parse(text);
} catch {
return text;
}
}
async function main() {
// Step 1: Initialize
const init = await callMCP("initialize", {});
console.log(`Connected to ${init.serverInfo.name} v${init.serverInfo.version}`);
// Step 2: List tools
const toolsList = await callMCP("tools/list", {});
console.log(`Available tools: ${toolsList.tools.map((t: any) => t.name).join(", ")}`);
// Step 3: Search listings
const searchResult = await callTool("search_listings", {
query: "lagoon",
category: "boats",
limit: 5,
});
console.log(`Found ${searchResult.results.length} listings`);
const topListing = searchResult.results[0];
console.log(`Top: ${topListing.title}`);
// Step 4: Get Markdown detail
const markdown = await callTool("get_listing_markdown", {
id_or_slug: topListing.slug,
});
console.log("\n--- Markdown ---");
console.log(typeof markdown === "string" ? markdown : JSON.stringify(markdown));
// Step 5: Create a listing (requires write API key)
if (!API_KEY) {
console.log("\nSkipping write operations — no API key set");
return;
}
const newListing = await callTool("create_listing", {
title: "Apple iPhone 13 128GB Blue",
description: "Apple iPhone 13 in blue, 128GB. Good condition, battery health 89%.",
category_id: "electronics",
condition: "good",
price: { amount: 399, currency: "EUR", negotiable: true },
location: { country: "ES", city: "Madrid", shipping: true },
tags: ["iphone", "apple", "smartphone"],
});
console.log(`Created listing: ${newListing.id}`);
// Step 6: Publish
const published = await callTool("publish_listing", {
id_or_slug: newListing.slug,
});
console.log(`Published: ${published.status}`);
}
main().catch(console.error);Python
# use_mcp_server.py
import requests
import json
import os
MCP_URL = "https://mcp.owning.pro/mcp"
API_KEY = os.environ.get("OWNING_API_KEY") # own_... format
_next_id = 1
def call_mcp(method, params=None):
global _next_id
headers = {"Content-Type": "application/json"}
if API_KEY:
headers["Authorization"] = f"Bearer {API_KEY}"
resp = requests.post(MCP_URL, headers=headers, json={
"jsonrpc": "2.0",
"id": _next_id,
"method": method,
"params": params or {},
})
_next_id += 1
data = resp.json()
if "error" in data:
raise Exception(data["error"]["message"])
return data["result"]
def call_tool(name, args):
result = call_mcp("tools/call", {"name": name, "arguments": args})
if result.get("isError"):
raise Exception(result["content"][0]["text"])
text = result["content"][0]["text"]
try:
return json.loads(text)
except json.JSONDecodeError:
return text
def main():
# Step 1: Initialize
init = call_mcp("initialize", {})
info = init["serverInfo"]
print(f"Connected to {info['name']} v{info['version']}")
# Step 2: List tools
tools_list = call_mcp("tools/list", {})
tool_names = [t["name"] for t in tools_list["tools"]]
print(f"Available tools: {', '.join(tool_names)}")
# Step 3: Search listings
search_result = call_tool("search_listings", {
"query": "lagoon",
"category": "boats",
"limit": 5,
})
print(f"Found {len(search_result['results'])} listings")
top = search_result["results"][0]
print(f"Top: {top['title']}")
# Step 4: Get Markdown detail
markdown = call_tool("get_listing_markdown", {
"id_or_slug": top["slug"],
})
print("\n--- Markdown ---")
if isinstance(markdown, str):
print(markdown)
else:
print(json.dumps(markdown, indent=2))
# Step 5: Create a listing (requires write API key)
if not API_KEY:
print("\nSkipping write operations — no API key set")
return
new_listing = call_tool("create_listing", {
"title": "Apple iPhone 13 128GB Blue",
"description": "Apple iPhone 13 in blue, 128GB. Good condition, battery health 89%.",
"category_id": "electronics",
"condition": "good",
"price": {"amount": 399, "currency": "EUR", "negotiable": True},
"location": {"country": "ES", "city": "Madrid", "shipping": True},
"tags": ["iphone", "apple", "smartphone"],
})
print(f"Created listing: {new_listing['id']}")
# Step 6: Publish
published = call_tool("publish_listing", {
"id_or_slug": new_listing["slug"],
})
print(f"Published: {published['status']}")
if __name__ == "__main__":
main()예제 4: 분석 이벤트 추적
시나리오: 프론트엔드 애플리케이션이 페이지뷰, 검색, 가입, 등록글 조회를 추적하기 위해 분석 이벤트를 Owning에 전송합니다. 이 엔드포인트는 공개이며 fire-and-forget입니다 — 항상 202 Accepted를 반환합니다.
클라이언트 IP는 저장 전 해시(SHA-256 + salt)됩니다. 원시 IP는 저장되지 않습니다.
유효한 이벤트 타입
page_view— includepath,referrersignup— includeuser_idlisting_created— includeuser_id,listing_idsearch— includequery,results_countlisting_view— includelisting_id
curl
# Track a page view
curl -s -X POST https://api.owning.pro/api/track -H "Content-Type: application/json" -d '{"event":"page_view","path":"/listings/bavaria-s36-coupe","referrer":"https://google.com"}'
# → 202 {"ok":true}
# Track a search
curl -s -X POST https://api.owning.pro/api/track -H "Content-Type: application/json" -d '{"event":"search","query":"yacht","results_count":142,"path":"/listings?q=yacht"}'
# → 202 {"ok":true}TypeScript
// Fire-and-forget analytics tracking
function track(event: string, data: Record<string, unknown> = {}) {
fetch("https://api.owning.pro/api/track", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ event, ...data }),
}).catch(() => {}); // silently fail — best-effort
}
track("page_view", { path: window.location.pathname, referrer: document.referrer });
track("search", { query: "yacht", results_count: 142 });Python
import requests
def track(event: str, **data):
"""Send analytics event (fire-and-forget)."""
try:
requests.post("https://api.owning.pro/api/track",
json={"event": event, **data}, timeout=5)
except Exception:
pass # best-effort
track("page_view", path="/listings", referrer="https://google.com")
track("search", query="yacht", results_count=142)예제 5: 등록글 프로모션 (프리미엄)
시나리오: 판매자가 Stripe 체크아웃으로 추천 프리미엄 프로모션으로 등록글을 프로모션하고, 프리미엄 상태를 확인합니다.
프리미엄 등록글 DB 스키마(premium_type, premium_until)가 라이브입니다. Stripe 체크아웃과 웹훅 엔드포인트가 스캐폴드 중. 예제는 계획된 API 표면을 보여줍니다.
프리미엄 타입
featured— top of search results and category pagesurgent— urgent badge and priority placement
curl (계획됨)
# Step 1: Initiate premium checkout (JWT — listing owner only)
curl -s -X POST https://api.owning.pro/api/listings/lst_01J.../premium -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" -d '{"premium_type":"featured","duration_days":7}'
# → 200 {"checkout_url":"https://checkout.stripe.com/c/pay/cs_...","session_id":"cs_...","premium_type":"featured","amount":499,"currency":"eur","duration_days":7}
# Step 2: Check premium status (public)
curl -s https://api.owning.pro/api/listings/lst_01J.../premium-status
# → 200 {"listing_id":"lst_01J...","premium_type":"featured","premium_until":"2026-07-18T18:00:00.000Z","is_active":true}TypeScript (계획됨)
async function promoteListing(listingId: string, jwt: string, premiumType: "featured" | "urgent", durationDays = 7) {
const res = await fetch(`https://api.owning.pro/api/listings/${listingId}/premium`, {
method: "POST",
headers: { "Authorization": `Bearer ${jwt}`, "Content-Type": "application/json" },
body: JSON.stringify({ premium_type: premiumType, duration_days: durationDays }),
});
const data = await res.json();
window.location.href = data.checkout_url; // redirect to Stripe
}
async function getPremiumStatus(listingId: string) {
const res = await fetch(`https://api.owning.pro/api/listings/${listingId}/premium-status`);
return res.json();
}Python (계획됨)
import requests
def promote_listing(listing_id: str, jwt: str, premium_type: str, duration_days: int = 7):
res = requests.post(f"https://api.owning.pro/api/listings/{listing_id}/premium",
headers={"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"},
json={"premium_type": premium_type, "duration_days": duration_days})
return res.json()
def get_premium_status(listing_id: str):
res = requests.get(f"https://api.owning.pro/api/listings/{listing_id}/premium-status")
return res.json()팁 & 모범 사례
REST API와 MCP 서버 중 선택
| REST API를 사용할 때… | MCP 서버를 사용할 때… |
|---|---|
| HTTP에 대한 세밀한 제어가 필요(커스텀 헤더, 스트리밍, 캐싱) | LLM을 사용하여 동작을 결정하는 AI 에이전트를 구축 중 |
| 기존 HTTP 기반 코드베이스에 통합 중 | LLM을 위한 도구 설명과 타입된 매개변수를 원함 |
| MCP 도구가 다루지 않는 엔드포인트가 필요(예: 인증, 스키마) | HTTP 보일러플레이트 없이 더 간단한 코드를 원함 |
| Markdown/JSON 피드를 직접 소비 중 | MCP를 기본 지원하는 클라이언트(Claude Desktop, Cursor 등)를 사용 중 |
속도 제한 관리
- 검색 결과 캐시 — 반복 검색 시 로컬에 캐시하여 속도 제한 도달을 피하세요.
- 더 높은 제한을 위해 API 키 사용 — 인증된 요청은 미인증 100 req/min 대비 300 req/min을 받습니다.
- 이미지 업로드 일괄 처리 — 하루 50개 업로드. 등록글 생성 전 모든 이미지를 업로드하세요.
- 429 응답 처리 — Retry-After 헤더를 확인하고 그에 따라 백오프하세요.
보안
- API 키 하드코딩 금지 — 환경 변수나 시크릿 매니저를 사용하세요.
- 최소 권한 사용 — 읽기만 필요하면 읽기 전용 키를 생성하세요.
- 정기적 키 교체 — 오래된 키를 삭제하고 주기적으로 새 키를 생성하세요.
- HTTPS만 사용 — 모든 Owning 엔드포인트는 HTTPS입니다. HTTP를 사용하지 마세요.
전체 엔드포인트 문서: API 참조
MCP 도구 상세: MCP 서버 빠른 시작