Perplexity Sonar API 개발자 가이드: 애플리케이션에 AI 검색 기능 구축하기

Perplexity Sonar API란 무엇인가

Perplexity Sonar API는 실시간 웹 검색과 대규모 언어 모델(LLM)을 결합하여, 출처가 명시된 정확한 답변을 프로그래밍 방식으로 제공하는 검색 특화 API이다. 기존의 검색 엔진 API가 링크 목록만 반환하는 것과 달리, Sonar API는 검색 결과를 종합하여 자연어로 구성된 답변과 함께 인용(citation)을 제공한다.

Sonar API가 개발자에게 유용한 핵심 이유는 다음과 같다.

  • 실시간 웹 검색 통합: 모델이 자체적으로 웹을 검색하여 최신 정보를 반환한다. RAG(Retrieval-Augmented Generation) 파이프라인을 직접 구축할 필요가 없다.
  • 인용 기반 응답: 모든 답변에 출처 URL이 포함되어, 사용자에게 신뢰할 수 있는 정보를 제공할 수 있다.
  • OpenAI 호환 인터페이스: chat completions 형식을 따르므로, 기존 OpenAI SDK 기반 코드에서 엔드포인트와 API 키만 변경하면 바로 사용할 수 있다.
  • 다양한 모델 티어: 빠른 응답이 필요한 경우부터 심층 리서치까지, 용도에 맞는 모델을 선택할 수 있다.

이 가이드에서는 API 키 발급부터 프로덕션 수준의 검색 기능 구축까지 전 과정을 다룬다.

시작하기: API 키 발급과 첫 번째 호출

API 키 발급

Perplexity API를 사용하려면 먼저 API 키를 발급받아야 한다.

  1. Perplexity API 설정 페이지에 접속한다.
  2. 결제 정보를 등록하고 크레딧을 충전한다. Sonar API는 사용량 기반 과금 방식이다.
  3. API 키를 생성하고 안전한 장소에 저장한다. 키는 pplx- 접두사로 시작한다.

첫 번째 API 호출 (Python)

Python에서 requests 라이브러리를 사용한 기본 호출 예시이다.

import requests
import json

API_KEY = "pplx-your-api-key-here"
URL = "https://api.perplexity.ai/chat/completions"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

payload = {
    "model": "sonar",
    "messages": [
        {
            "role": "system",
            "content": "당신은 정확하고 간결한 답변을 제공하는 검색 어시스턴트입니다."
        },
        {
            "role": "user",
            "content": "2026년 한국의 AI 규제 정책 현황은?"
        }
    ]
}

response = requests.post(URL, headers=headers, json=payload)
data = response.json()

# 답변 출력
print(data["choices"][0]["message"]["content"])

# 인용 출력
if "citations" in data:
    for i, url in enumerate(data["citations"], 1):
        print(f"[{i}] {url}")

첫 번째 API 호출 (JavaScript)

Node.js 환경에서 fetch를 사용하는 예시이다.

const API_KEY = "pplx-your-api-key-here";
const URL = "https://api.perplexity.ai/chat/completions";

async function searchWithSonar(query) {
  const response = await fetch(URL, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "sonar",
      messages: [
        {
          role: "system",
          content: "정확하고 간결한 답변을 제공하는 검색 어시스턴트입니다."
        },
        {
          role: "user",
          content: query
        }
      ]
    })
  });

  const data = await response.json();
  return {
    answer: data.choices[0].message.content,
    citations: data.citations || []
  };
}

// 사용 예시
const result = await searchWithSonar("2026년 한국의 AI 규제 정책 현황은?");
console.log("답변:", result.answer);
result.citations.forEach((url, i) => {
  console.log(`[${i + 1}] ${url}`);
});

응답 구조 이해

Sonar API의 응답은 OpenAI chat completions 형식을 기반으로 하되, 검색 관련 필드가 추가되어 있다. 주요 필드는 다음과 같다.

{
  "id": "chatcmpl-abc123",
  "model": "sonar",
  "object": "chat.completion",
  "created": 1711504800,
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "2026년 한국 정부는 AI 기본법을 시행하며..."
      }
    }
  ],
  "citations": [
    "https://example.com/ai-regulation-korea-2026",
    "https://example.com/ai-basic-act"
  ],
  "usage": {
    "prompt_tokens": 42,
    "completion_tokens": 256,
    "total_tokens": 298
  }
}
  • choices[0].message.content: 검색 결과를 종합한 자연어 답변이다. 본문 내에서 [1], [2]와 같은 인용 번호가 표시된다.
  • citations: 답변에서 참조한 웹 소스의 URL 목록이다. 본문의 인용 번호와 이 배열의 인덱스가 대응한다.
  • usage: 토큰 사용량 정보로, 비용 추적에 활용한다.

모델 선택: Sonar, Sonar Pro, Sonar Deep Research

Perplexity는 용도에 따라 세 가지 Sonar 모델을 제공한다. 아래 표에서 각 모델의 특성을 비교할 수 있다.

항목SonarSonar ProSonar Deep Research
용도빠른 팩트 체크, 간단한 질문복합적인 질문, 다중 소스 종합심층 리서치, 보고서 수준 분석
응답 속도빠름 (1-3초)보통 (3-8초)느림 (30초-수 분)
검색 깊이기본 웹 검색심화 웹 검색, 더 많은 소스 참조다단계 검색, 반복 탐색
인용 수3-5개5-15개10-30개
모델명sonarsonar-prosonar-deep-research
비용낮음중간높음
적합한 시나리오챗봇, 빠른 Q&A비교 분석, 시장 조사 요약경쟁사 분석, 기술 보고서 작성

모델 선택 기준을 정리하면 다음과 같다.

  • 응답 시간이 중요한 실시간 대화 인터페이스에는 sonar를 사용한다. 사용자가 즉각적인 응답을 기대하는 챗봇이나 검색 바에 적합하다.
  • 정확성과 깊이가 중요한 리서치 도구에는 sonar-pro를 사용한다. 여러 소스를 교차 검증하여 더 신뢰할 수 있는 답변을 제공한다.
  • 종합 보고서 수준의 분석이 필요한 경우에는 sonar-deep-research를 사용한다. 비동기 처리가 적합하며, 사용자에게 “분석 중” 상태를 표시하는 것이 좋다.

고급 기능

스트리밍 응답

긴 응답을 실시간으로 표시하려면 스트리밍을 활성화한다. 사용자 경험을 크게 개선할 수 있다.

import requests
import json

def stream_search(query):
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }

    payload = {
        "model": "sonar",
        "messages": [
            {"role": "user", "content": query}
        ],
        "stream": True
    }

    response = requests.post(
        "https://api.perplexity.ai/chat/completions",
        headers=headers,
        json=payload,
        stream=True
    )

    full_content = ""
    for line in response.iter_lines():
        if line:
            line_text = line.decode("utf-8")
            if line_text.startswith("data: "):
                data_str = line_text[6:]
                if data_str.strip() == "[DONE]":
                    break
                chunk = json.loads(data_str)
                delta = chunk["choices"][0].get("delta", {})
                content = delta.get("content", "")
                if content:
                    print(content, end="", flush=True)
                    full_content += content

    print()
    return full_content

JavaScript에서는 다음과 같이 구현한다.

async function streamSearch(query) {
  const response = await fetch("https://api.perplexity.ai/chat/completions", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "sonar",
      messages: [{ role: "user", content: query }],
      stream: true
    })
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let fullContent = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split("\n").filter(line => line.startsWith("data: "));

    for (const line of lines) {
      const dataStr = line.slice(6);
      if (dataStr.trim() === "[DONE]") return fullContent;

      const chunk = JSON.parse(dataStr);
      const content = chunk.choices[0]?.delta?.content || "";
      if (content) {
        process.stdout.write(content);
        fullContent += content;
      }
    }
  }

  return fullContent;
}

도메인 필터링

특정 도메인의 소스만 포함하거나 제외할 수 있다. 신뢰할 수 있는 소스로 검색 범위를 제한하고 싶을 때 유용하다.

payload = {
    "model": "sonar",
    "messages": [
        {"role": "user", "content": "최근 반도체 산업 동향"}
    ],
    "search_domain_filter": ["reuters.com", "bloomberg.com", "ft.com"]
}

특정 도메인을 제외하려면 도메인명 앞에 - 접두사를 붙인다.

payload = {
    "model": "sonar",
    "messages": [
        {"role": "user", "content": "최근 반도체 산업 동향"}
    ],
    "search_domain_filter": ["-reddit.com", "-quora.com"]
}

최신성 필터 (search_recency_filter)

검색 결과의 시간 범위를 제한하여 최신 정보만 가져올 수 있다. 뉴스나 트렌드 분석에 특히 유용하다.

payload = {
    "model": "sonar",
    "messages": [
        {"role": "user", "content": "이번 주 AI 산업 주요 뉴스"}
    ],
    "search_recency_filter": "week"  # "month", "week", "day", "hour" 중 선택
}

사용 가능한 옵션은 다음과 같다.

  • "month": 최근 한 달 이내 결과
  • "week": 최근 일주일 이내 결과
  • "day": 최근 하루 이내 결과
  • "hour": 최근 한 시간 이내 결과

시스템 프롬프트 활용

시스템 프롬프트를 통해 응답의 형식, 언어, 톤을 제어할 수 있다. 제품에 맞는 일관된 출력을 유지하는 데 핵심적인 역할을 한다.

payload = {
    "model": "sonar",
    "messages": [
        {
            "role": "system",
            "content": (
                "당신은 한국 스타트업 투자자를 위한 시장 조사 어시스턴트입니다. "
                "답변은 반드시 한국어로 작성하고, 핵심 수치와 데이터를 먼저 제시합니다. "
                "모든 주장에는 출처를 명시하며, 불확실한 정보는 명확히 구분합니다. "
                "답변 마지막에 '핵심 요약' 섹션을 3줄 이내로 추가합니다."
            )
        },
        {
            "role": "user",
            "content": "2026년 한국 SaaS 시장 규모와 성장률"
        }
    ]
}

온도(temperature) 설정

응답의 창의성 수준을 조절할 수 있다. 검색 기반 팩트 확인에는 낮은 값을, 브레인스토밍에는 높은 값을 사용한다.

payload = {
    "model": "sonar",
    "messages": [
        {"role": "user", "content": "양자 컴퓨팅의 실용적 응용 사례"}
    ],
    "temperature": 0.1  # 0.0에서 2.0 사이, 기본값은 0.2
}

프로덕션 기능 구축

리서치 어시스턴트 구현

사용자의 질문을 받아 검색하고, 인용과 함께 구조화된 응답을 제공하는 리서치 어시스턴트를 구축해 보겠다.

import requests
import json
from dataclasses import dataclass
from typing import Optional


@dataclass
class SearchResult:
    answer: str
    citations: list[str]
    model: str
    tokens_used: int


class SonarResearchAssistant:
    BASE_URL = "https://api.perplexity.ai/chat/completions"

    def __init__(self, api_key: str, default_model: str = "sonar"):
        self.api_key = api_key
        self.default_model = default_model
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        })

    def search(
        self,
        query: str,
        model: Optional[str] = None,
        system_prompt: Optional[str] = None,
        recency: Optional[str] = None,
        domain_filter: Optional[list[str]] = None,
        temperature: float = 0.2
    ) -> SearchResult:
        messages = []

        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})

        messages.append({"role": "user", "content": query})

        payload = {
            "model": model or self.default_model,
            "messages": messages,
            "temperature": temperature
        }

        if recency:
            payload["search_recency_filter"] = recency

        if domain_filter:
            payload["search_domain_filter"] = domain_filter

        response = self.session.post(self.BASE_URL, json=payload)
        response.raise_for_status()
        data = response.json()

        return SearchResult(
            answer=data["choices"][0]["message"]["content"],
            citations=data.get("citations", []),
            model=data["model"],
            tokens_used=data["usage"]["total_tokens"]
        )

    def deep_research(self, query: str) -> SearchResult:
        """심층 리서치가 필요한 질문에 사용한다."""
        return self.search(
            query=query,
            model="sonar-deep-research",
            system_prompt=(
                "심층적이고 포괄적인 분석을 제공합니다. "
                "여러 소스를 교차 검증하고, 상반되는 관점이 있으면 함께 제시합니다."
            ),
            temperature=0.1
        )

    def quick_fact_check(self, claim: str) -> SearchResult:
        """빠른 팩트 체크에 사용한다."""
        return self.search(
            query=f"다음 주장의 사실 여부를 검증해 주세요: {claim}",
            model="sonar",
            system_prompt=(
                "팩트 체커로서 주장의 진위를 판별합니다. "
                "'사실', '거짓', '부분적 사실', '확인 불가' 중 하나로 판정하고, "
                "근거를 제시합니다."
            ),
            temperature=0.0
        )

인용 표시 처리

Sonar API 응답의 인용 번호를 사용자에게 표시하는 로직을 구현한다. 프론트엔드에서 클릭 가능한 링크로 변환하는 것이 일반적이다.

import re


def parse_citations(content: str, citations: list[str]) -> str:
    """본문의 [1], [2] 등을 실제 URL 링크로 변환한다."""

    def replace_citation(match):
        index = int(match.group(1)) - 1
        if 0 <= index < len(citations):
            url = citations[index]
            return f'[{match.group(1)}]({url})'
        return match.group(0)

    return re.sub(r'\[(\d+)\]', replace_citation, content)


def format_response_with_sources(content: str, citations: list[str]) -> dict:
    """응답을 본문과 소스 목록으로 분리하여 구조화한다."""
    return {
        "content": content,
        "sources": [
            {"index": i + 1, "url": url}
            for i, url in enumerate(citations)
        ],
        "content_with_links": parse_citations(content, citations)
    }

JavaScript에서 프론트엔드 렌더링 시 인용을 처리하는 예시이다.

function renderCitations(content, citations) {
  // [1], [2] 등을 클릭 가능한 링크로 변환
  const rendered = content.replace(/\[(\d+)\]/g, (match, num) => {
    const index = parseInt(num) - 1;
    if (index >= 0 && index < citations.length) {
      const url = citations[index];
      return `<a href="${url}" target="_blank" rel="noopener noreferrer"
                class="citation-link" title="${url}">[${num}]</a>`;
    }
    return match;
  });

  // 소스 목록 생성
  const sourceList = citations.map((url, i) => {
    const domain = new URL(url).hostname.replace("www.", "");
    return `<li><a href="${url}" target="_blank">[${i + 1}] ${domain}</a></li>`;
  }).join("\n");

  return {
    html: rendered,
    sourceListHtml: `<ol class="citation-sources">${sourceList}</ol>`
  };
}

오류 처리와 재시도

프로덕션 환경에서는 네트워크 오류, 레이트 리밋, 서버 오류에 대한 적절한 처리가 필수적이다.

import time
import requests
from requests.exceptions import RequestException


class SonarAPIClient:
    def __init__(self, api_key: str, max_retries: int = 3):
        self.api_key = api_key
        self.max_retries = max_retries
        self.base_url = "https://api.perplexity.ai/chat/completions"

    def _make_request(self, payload: dict) -> dict:
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }

        for attempt in range(self.max_retries):
            try:
                response = requests.post(
                    self.base_url,
                    headers=headers,
                    json=payload,
                    timeout=60
                )

                if response.status_code == 200:
                    return response.json()

                if response.status_code == 429:
                    # 레이트 리밋: Retry-After 헤더를 확인한다
                    retry_after = int(response.headers.get("Retry-After", 5))
                    print(f"레이트 리밋 도달. {retry_after}초 후 재시도합니다.")
                    time.sleep(retry_after)
                    continue

                if response.status_code >= 500:
                    # 서버 오류: 지수 백오프로 재시도한다
                    wait_time = (2 ** attempt) + 1
                    print(f"서버 오류 ({response.status_code}). {wait_time}초 후 재시도합니다.")
                    time.sleep(wait_time)
                    continue

                # 4xx 클라이언트 오류 (429 제외): 재시도하지 않는다
                response.raise_for_status()

            except RequestException as e:
                if attempt < self.max_retries - 1:
                    wait_time = (2 ** attempt) + 1
                    print(f"요청 실패: {e}. {wait_time}초 후 재시도합니다.")
                    time.sleep(wait_time)
                else:
                    raise

        raise Exception(f"{self.max_retries}회 시도 후에도 요청이 실패했습니다.")

캐싱 전략

동일한 쿼리가 반복되는 경우 캐싱을 통해 비용을 절감하고 응답 시간을 단축할 수 있다. 검색 결과의 특성상 TTL(Time To Live)을 적절히 설정하는 것이 중요하다.

import hashlib
import json
import time
from functools import lru_cache


class CachedSonarClient:
    def __init__(self, api_key: str, cache_ttl: int = 3600):
        self.client = SonarAPIClient(api_key)
        self.cache = {}
        self.cache_ttl = cache_ttl  # 기본 1시간

    def _cache_key(self, query: str, model: str, recency: str = None) -> str:
        """쿼리와 설정을 기반으로 캐시 키를 생성한다."""
        key_data = json.dumps({
            "query": query,
            "model": model,
            "recency": recency
        }, sort_keys=True)
        return hashlib.sha256(key_data.encode()).hexdigest()

    def search(self, query: str, model: str = "sonar",
               recency: str = None, bypass_cache: bool = False) -> dict:
        cache_key = self._cache_key(query, model, recency)

        # 캐시 확인
        if not bypass_cache and cache_key in self.cache:
            entry = self.cache[cache_key]
            if time.time() - entry["timestamp"] < self.cache_ttl:
                return entry["data"]

        # API 호출
        payload = {
            "model": model,
            "messages": [{"role": "user", "content": query}]
        }
        if recency:
            payload["search_recency_filter"] = recency

        result = self.client._make_request(payload)

        # 캐시 저장
        self.cache[cache_key] = {
            "data": result,
            "timestamp": time.time()
        }

        return result

캐시 TTL 설정 권장 사항은 다음과 같다.

쿼리 유형권장 TTL이유
속보 / 실시간 뉴스5-15분빠르게 변하는 정보
일반 뉴스1-4시간적당한 최신성 유지
기술 문서 / 가이드12-24시간자주 변하지 않는 정보
역사적 사실24-72시간거의 변하지 않는 정보

레이트 리밋 관리

Sonar API는 분당 요청 수에 제한이 있다. 프로덕션 환경에서는 토큰 버킷 방식의 레이트 리미터를 구현하는 것이 좋다.

import threading
import time


class RateLimiter:
    """토큰 버킷 방식의 레이트 리미터."""

    def __init__(self, requests_per_minute: int = 50):
        self.capacity = requests_per_minute
        self.tokens = requests_per_minute
        self.refill_rate = requests_per_minute / 60.0  # 초당 토큰 보충량
        self.last_refill = time.time()
        self.lock = threading.Lock()

    def acquire(self, timeout: float = 30.0) -> bool:
        """토큰을 획득한다. 성공하면 True, 타임아웃이면 False를 반환한다."""
        deadline = time.time() + timeout

        while time.time() < deadline:
            with self.lock:
                self._refill()
                if self.tokens >= 1:
                    self.tokens -= 1
                    return True

            time.sleep(0.1)

        return False

    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now

비용 관리

Sonar API는 입력 토큰과 출력 토큰에 따라 과금된다. 비용을 효과적으로 관리하기 위한 방법을 정리한다.

모델별 비용 구조

모델입력 토큰 단가 (1M 토큰당)출력 토큰 단가 (1M 토큰당)검색 요청당 추가 비용
sonar$1$1$5 / 1000 요청
sonar-pro$3$15$5 / 1000 요청
sonar-deep-research$2$8요청당 가변

위 금액은 변경될 수 있으므로, 최신 요금은 Perplexity 공식 문서를 확인하는 것이 좋다.

비용 최적화 전략

  1. 모델 티어 분리: 간단한 질문은 sonar, 복잡한 질문만 sonar-pro를 사용하도록 라우팅 로직을 구현한다.
def select_model(query: str) -> str:
    """쿼리 복잡도에 따라 적합한 모델을 선택한다."""
    complex_keywords = ["비교", "분석", "추세", "전망", "종합", "차이점"]
    deep_keywords = ["심층 분석", "보고서", "상세 조사", "전수 조사"]

    if any(kw in query for kw in deep_keywords):
        return "sonar-deep-research"
    elif any(kw in query for kw in complex_keywords) or len(query) > 100:
        return "sonar-pro"
    else:
        return "sonar"
  1. max_tokens 제한: 필요 이상으로 긴 응답을 방지하여 출력 토큰 비용을 절약한다.
payload = {
    "model": "sonar",
    "messages": [{"role": "user", "content": query}],
    "max_tokens": 500  # 간결한 답변이 필요한 경우 제한을 둔다
}
  1. 사용량 모니터링: 일일/월간 사용량을 추적하고 임계치를 설정한다.
class UsageTracker:
    def __init__(self, daily_budget_usd: float = 10.0):
        self.daily_budget = daily_budget_usd
        self.daily_tokens = 0
        self.daily_requests = 0
        self.reset_date = time.strftime("%Y-%m-%d")

    def record(self, usage: dict):
        today = time.strftime("%Y-%m-%d")
        if today != self.reset_date:
            self.daily_tokens = 0
            self.daily_requests = 0
            self.reset_date = today

        self.daily_tokens += usage.get("total_tokens", 0)
        self.daily_requests += 1

    def is_within_budget(self) -> bool:
        # 대략적인 비용 추정 (sonar 기준)
        estimated_cost = (self.daily_tokens / 1_000_000) * 1.0
        estimated_cost += (self.daily_requests / 1000) * 5.0
        return estimated_cost < self.daily_budget

자주 묻는 질문 (FAQ)

Sonar API는 OpenAI SDK와 호환되는가?

그렇다. Sonar API는 OpenAI의 chat completions 형식을 따르므로, 기존 OpenAI Python SDK 또는 JavaScript SDK에서 base URL과 API 키만 변경하면 사용할 수 있다.

from openai import OpenAI

client = OpenAI(
    api_key="pplx-your-api-key",
    base_url="https://api.perplexity.ai"
)

response = client.chat.completions.create(
    model="sonar",
    messages=[{"role": "user", "content": "오늘 코스피 지수는?"}]
)

다만, citations 필드 등 Sonar 전용 응답 필드는 OpenAI SDK 타입에 포함되어 있지 않으므로, 원시 응답 딕셔너리에서 직접 접근해야 한다.

한국어 쿼리와 응답을 지원하는가?

Sonar API는 다국어를 지원한다. 한국어로 질문하면 한국어로 답변하며, 시스템 프롬프트에서 언어를 명시적으로 지정할 수도 있다. 한국어 웹 소스도 검색 대상에 포함되며, 도메인 필터를 활용하여 한국어 소스의 비중을 높일 수 있다.

스트리밍과 일반 응답 중 어떤 것을 사용해야 하는가?

사용자 인터페이스가 있는 애플리케이션에서는 스트리밍을 권장한다. 특히 sonar-prosonar-deep-research처럼 응답 시간이 긴 모델에서는 스트리밍이 사용자 체감 대기 시간을 크게 줄여 준다. 반면, 백엔드 자동화 파이프라인이나 배치 처리에서는 일반 응답이 코드 구현이 단순하다.

인용이 포함되지 않는 경우가 있는가?

드물지만, 모델이 자체 지식만으로 답변할 수 있다고 판단하거나 검색 결과가 충분하지 않은 경우 인용이 비어 있을 수 있다. 프로덕션 코드에서는 citations 필드가 빈 배열이거나 존재하지 않는 경우를 반드시 처리해야 한다.

API 키 보안은 어떻게 관리해야 하는가?

API 키는 절대 클라이언트 사이드 코드(프론트엔드 JavaScript)에 노출해서는 안 된다. 반드시 서버 사이드에서 API 호출을 수행하고, 환경 변수나 시크릿 매니저를 통해 키를 관리한다. .env 파일에 저장하는 경우 .gitignore에 반드시 추가한다.

import os

API_KEY = os.environ.get("PERPLEXITY_API_KEY")
if not API_KEY:
    raise ValueError("PERPLEXITY_API_KEY 환경 변수가 설정되지 않았습니다.")

동시 요청 제한은 얼마인가?

기본 레이트 리밋은 플랜에 따라 다르다. 일반적으로 분당 50회 요청부터 시작하며, 더 높은 처리량이 필요한 경우 Perplexity 팀에 엔터프라이즈 플랜을 문의할 수 있다. 레이트 리밋에 도달하면 HTTP 429 응답이 반환되며, Retry-After 헤더에 권장 대기 시간이 포함된다.

Sonar API를 기존 RAG 파이프라인과 함께 사용할 수 있는가?

가능하다. Sonar API를 외부 검색 도구로 활용하고, 자체 벡터 데이터베이스의 내부 문서 검색 결과와 결합하는 하이브리드 접근이 효과적이다. 예를 들어, 내부 지식 기반에서 관련 문서를 먼저 검색한 뒤, Sonar API로 최신 외부 정보를 보완하는 방식으로 구성할 수 있다.

정리

Perplexity Sonar API는 웹 검색과 LLM을 결합한 강력한 도구로, 인용이 포함된 신뢰할 수 있는 검색 기능을 애플리케이션에 빠르게 통합할 수 있게 해 준다. 핵심 사항을 다시 정리하면 다음과 같다.

  • 모델 선택: 속도가 중요하면 sonar, 깊이가 중요하면 sonar-pro, 종합 분석이 필요하면 sonar-deep-research를 사용한다.
  • 인용 처리: 모든 응답에 포함된 출처 URL을 사용자에게 표시하여 신뢰성을 확보한다.
  • 프로덕션 준비: 레이트 리밋 관리, 지수 백오프 재시도, 응답 캐싱, 비용 모니터링을 구현한다.
  • 고급 기능: 도메인 필터, 최신성 필터, 스트리밍을 활용하여 사용자 경험을 최적화한다.

공식 문서는 Perplexity API Documentation에서 확인할 수 있으며, 최신 모델 목록과 요금 정보도 해당 페이지에서 관리된다.

다른 도구 둘러보기

Antigravity AI 콘텐츠 파이프라인 자동화 가이드: Google Docs에서 WordPress 퍼블리싱까지 가이드 Bolt.new 사례 연구: 마케팅 에이전시가 하루 만에 클라이언트 대시보드 5개 구축 사례 Bolt.new 베스트 프랙티스: 자연어 프롬프트로 풀스택 앱 빠르게 생성하기 모범사례 ChatGPT 고급 데이터 분석(코드 인터프리터) 완벽 가이드: 업로드부터 시각화까지 가이드 ChatGPT Custom GPTs 고급 가이드: Actions, API 통합, 지식 베이스 설정 가이드 ChatGPT 음성 모드 가이드: 음성 중심 고객 서비스와 내부 워크플로우 구축 가이드 Claude API 프로덕션 챗봇 가이드: 안정적인 AI 어시스턴트를 위한 시스템 프롬프트 아키텍처 가이드 Claude Artifacts 활용 베스트 프랙티스: 인터랙티브 대시보드, 문서, 코드 미리보기 만들기 모범사례 Claude Code Hooks 가이드: Pre/Post 실행 훅으로 커스텀 워크플로우 자동화하기 가이드 Claude MCP 서버 설정 가이드: Claude Code와 Desktop을 위한 커스텀 도구 통합 가이드 Cursor 사례 연구: 1인 창업자가 AI 코딩으로 2주 만에 Next.js SaaS MVP 구축 사례 Cursor Composer 완벽 가이드: 멀티 파일 편집, 인라인 Diff, 에이전트 모드 가이드 Cursor Rules 고급 가이드: 프로젝트별 AI 설정과 팀 코딩 표준 가이드 Devin AI 팀 워크플로우 통합 베스트 프랙티스: Slack, GitHub, 코드 리뷰 자동화 모범사례 Devin 사례 연구: 500개 패키지 Python 모노레포 의존성 자동 업그레이드 사례 ElevenLabs 사례 연구: 에드테크 스타트업이 6주 만에 200시간 강의를 8개 언어로 현지화 사례 ElevenLabs 다국어 더빙 가이드: 글로벌 콘텐츠를 위한 자동화된 영상 현지화 워크플로우 가이드 ElevenLabs Voice Design 완벽 가이드: 게임, 팟캐스트, 앱을 위한 일관된 캐릭터 음성 만들기 가이드 Gemini 2.5 Pro vs Claude Sonnet 4 vs GPT-4o: AI 코드 생성 비교 2026 비교 Gemini API 멀티모달 개발자 가이드: 이미지, 비디오, 문서 분석 코드 예제 가이드