Skip to content

통합 예제

Owning.pro와 가장 일반적인 통합 패턴을 다루는 세 가지 엔드투엔드 예제. 각 예제는 curl, TypeScript, Python 구현을 포함합니다.

#예제인증도구 / 엔드포인트
1Search & EvaluateNone (public)GET /api/listings, GET /api/listings/:id.md
2Publish a ListingAPI key (write)POST /api/api-keys, POST /api/upload, POST /api/listings, POST /api/listings/:id/publish
3Use MCP ServerAPI key (optional)search_listings, get_listing_markdown, create_listing, publish_listing
4Track Analytics EventsNone (public)POST /api/track
5Promote 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 evaluate

curl

# 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 sections

TypeScript

// 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 도구 호출을 통해.

MCP vs REST

이 예제는 예제 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 — include path, referrer
  • signup — include user_id
  • listing_created — include user_id, listing_id
  • search — include query, results_count
  • listing_view — include listing_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 pages
  • urgent — 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 서버 빠른 시작

코드 예제 — Owning.pro API | Owning Docs