ChatGPT를 자신의 웹 서비스에 통합하고 싶다면, OpenAI API를 직접 연동해야 한다. 다만 대부분의 개발자들은 API 키를 어디서 받는지, 요청 형식이 정확히 뭔지, 응답을 어떻게 처리하는지 모른 채 인터넷에서 가져온 코드를 그냥 복사해 붙인다. 이번에는 OpenAI API의 정확한 작동 원리, 인증부터 실제 메시지 송수신까지, 그리고 자주 마주치는 에러 처리 방법까지 완벽하게 정리해서 소개하겠다.

 

1단계. OpenAI API 키 발급받기
OpenAI의 공식 플랫폼에서 API 키를 생성하고 관리하는 것이 모든 것의 시작이다. 먼저 https://platform.openai.com/ 에 접속한 후 계정을 생성하거나 로그인한다. 이미 ChatGPT 계정이 있다면, 그 계정으로 접속하면 된다. 로그인 후 좌측 메뉴에서 "API keys" 항목을 찾아 클릭하고, "Create new secret key" 버튼을 눌러 새로운 API 키를 생성한다. 생성된 키는 한 번만 화면에 표시되므로, 반드시 어딘가에 안전하게 저장해두어야 한다. 이후 요청할 때마다 이 키를 사용한다.

사용 가능한 모델 목록은 https://platform.openai.com/docs/models 에서 확인할 수 있다. 2024년 기준 주로 사용되는 모델은 gpt-4, gpt-4-turbo, gpt-3.5-turbo 등이다. 무료 크레딧이 있을 수 있으니 대시보드의 "Usage" 섹션에서 현재 사용량과 남은 크레딧을 확인하는 것이 좋다.

 

2단계. PHP에서 cURL로 OpenAI API 호출하기
PHP에서 OpenAI API와 통신할 때는 cURL 라이브러리를 사용한다. 기본 구조는 다음과 같다: API 엔드포인트 URL 설정 → 요청 헤더에 API 키와 Content-Type 지정 → 요청 본문에 모델과 메시지 작성 → cURL 실행 → 응답 파싱.

 

✗ 잘못된 방법 - API 키를 코드에 하드코딩하고, 에러 처리가 없는 경우
<?php
$api_key = "sk-your-api-key-here"; // 절대 금지!
$url = "https://api.openai.com/v1/chat/completions";
$message = "Hello ChatGPT";

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    "Authorization: Bearer " . $api_key,
    "Content-Type: application/json"
));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array(
    "model" => "gpt-3.5-turbo",
    "messages" => array(
        array("role" => "user", "content" => $message)
    )
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
echo $response; // 응답을 그대로 출력, 매우 위험
?>

문제점: 1) API 키가 소스 코드에 노출된다. 2) 네트워크 에러나 API 에러에 대한 처리가 전혀 없다. 3) 응답 JSON을 파싱하지 않아 raw 데이터가 출력된다.

 

✓ 올바른 방법 - 환경 변수로 API 키 관리하고, 에러 처리를 포함
<?php
// .env 파일 또는 환경 변수에서 API 키를 읽음
$api_key = getenv("OPENAI_API_KEY");
if (empty($api_key)) {
    die("Error: OPENAI_API_KEY environment variable is not set.");
}

$url = "https://api.openai.com/v1/chat/completions";
$user_message = "Hello ChatGPT, what is PHP?";

// cURL 초기화
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    "Authorization: Bearer " . $api_key,
    "Content-Type: application/json"
));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array(
    "model" => "gpt-3.5-turbo",
    "messages" => array(
        array("role" => "user", "content" => $user_message)
    ),
    "max_tokens" => 150
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

// API 요청 실행
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);

// 네트워크 에러 처리
if ($curl_error) {
    die("cURL Error: " . $curl_error);
}

// HTTP 상태 코드 확인
if ($http_code !== 200) {
    $error_data = json_decode($response, true);
    die("API Error (" . $http_code . "): " . ($error_data['error']['message'] ?? 'Unknown error'));
}

// 응답 파싱
$data = json_decode($response, true);
if (isset($data['choices'][0]['message']['content'])) {
    $reply = $data['choices'][0]['message']['content'];
    echo "ChatGPT: " . htmlspecialchars($reply);
} else {
    die("Error: Unexpected API response format.");
}
?>

개선 사항: 1) API 키를 환경 변수에서 읽어 보안을 강화했다. 2) cURL 타임아웃을 설정했다(API 응답이 오래 걸릴 수 있으므로). 3) HTTP 상태 코드를 확인해 API 에러를 감지한다. 4) 응답을 JSON으로 파싱해 실제 메시지만 추출한다. 5) XSS 방지를 위해 htmlspecialchars() 를 사용한다.

 

3단계. 대화 이력 관리하기 - 멀티턴 대화 구현
ChatGPT의 진가는 단순한 질의응답이 아니라, 이전 메시지를 기억하는 "대화 이력" 관리에 있다. 사용자와 AI의 메시지를 모두 messages 배열에 담아 보내야 한다.

 

✗ 잘못된 예 - 매번 새로운 메시지만 보내는 경우
<?php
// 사용자가 "Python은 뭐야?"라고 물었을 때, 이전에 "PHP란 뭐야?"라고 물은 내용이 사라진다.
$user_input = "Python은 뭐야?";

$messages = array(
    array("role" => "user", "content" => $user_input)
);
// 이전 대화가 없음 → AI가 맥락을 모른다
?>

 

✓ 올바른 예 - 대화 이력을 유지하는 경우
<?php
// 세션을 통해 대화 이력을 유지
session_start();

if (!isset($_SESSION['chat_history'])) {
    $_SESSION['chat_history'] = array();
}

$api_key = getenv("OPENAI_API_KEY");
$user_input = "Python은 뭐야?";

// 사용자 메시지를 이력에 추가
$_SESSION['chat_history'][] = array(
    "role" => "user",
    "content" => $user_input
);

$url = "https://api.openai.com/v1/chat/completions";

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    "Authorization: Bearer " . $api_key,
    "Content-Type: application/json"
));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array(
    "model" => "gpt-3.5-turbo",
    "messages" => $_SESSION['chat_history'] // 전체 이력을 보냄
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code === 200) {
    $data = json_decode($response, true);
    $reply = $data['choices'][0]['message']['content'];
    
    // AI 응답을 이력에 추가
    $_SESSION['chat_history'][] = array(
        "role" => "assistant",
        "content" => $reply
    );
    
    echo "You: " . htmlspecialchars($user_input) . "<br />";
    echo "ChatGPT: " . htmlspecialchars($reply);
}
?>

개선 사항: 각 턴마다 사용자 메시지와 AI 응답을 모두 배열에 추가하므로, 다음 요청 시 이전 대화를 모두 참고한다. 이렇게 하면 AI가 "너는 방금 PHP에 대해 말했잖아, Python과의 차이를 설명해"라는 식의 맥락 있는 응답을 할 수 있다.

 

4단계. 실전 예제 - 웹 페이지에 채팅 UI 통합하기
HTML 폼에서 사용자 입력을 받아 AJAX로 전송하고, 응답을 실시간으로 표시하는 완성된 예제다.
<!DOCTYPE html>
<html>
<head>
    <title>ChatGPT Integration</title>
    <style>
        #chat-box { width: 500px; height: 400px; border: 1px solid #ccc; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
        .user-msg { background: #e3f2fd; margin: 5px 0; padding: 8px; border-radius: 5px; }
        .ai-msg { background: #f5f5f5; margin: 5px 0; padding: 8px; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>ChatGPT Chat</h1>
    <div id="chat-box"></div>
    <input type="text" id="user-input" placeholder="Type your message..." style="width: 400px; padding: 8px;" />
    <button onclick="sendMessage()">Send</button>
    
    <script>
        function sendMessage() {
            const userInput = document.getElementById('user-input').value;
            if (!userInput.trim()) return;
            
            // 사용자 메시지를 화면에 표시
            const chatBox = document.getElementById('chat-box');
            chatBox.innerHTML += '<div class="user-msg">You: ' + escapeHtml(userInput) + '</div>';
            document.getElementById('user-input').value = '';
            
            // AJAX로 서버에 전송
            fetch('chat_api.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ message: userInput })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    chatBox.innerHTML += '<div class="ai-msg">ChatGPT: ' + escapeHtml(data.reply) + '</div>';
                    chatBox.scrollTop = chatBox.scrollHeight;
                } else {
                    chatBox.innerHTML += '<div class="ai-msg">Error: ' + escapeHtml(data.error) + '</div>';
                }
            })
            .catch(err => console.error('Fetch error:', err));
        }
        
        function escapeHtml(text) {
            const map = { '&': '<>', '<': '<', '>': '>', '"': '"', "'": ''' };
            return text.replace(/[&<>"']/g, m => map[m]);
        }
        
        // Enter 키로도 전송 가능
        document.getElementById('user-input').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') sendMessage();
        });
    </script>
</body>
</html>

PHP 백엔드 (chat_api.php):

<?php
header('Content-Type: application/json');
session_start();

if (!isset($_SESSION['chat_history'])) {
    $_SESSION['chat_history'] = array();
}

$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['message'])) {
    echo json_encode(['success' => false, 'error' => 'No message provided']);
    exit;
}

$api_key = getenv('OPENAI_API_KEY');
if (empty($api_key)) {
    echo json_encode(['success' => false, 'error' => 'API key not configured']);
    exit;
}

$user_message = $data['message'];
$_SESSION['chat_history'][] = array('role' => 'user', 'content' => $user_message);

$url = 'https://api.openai.com/v1/chat/completions';

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Authorization: Bearer ' . $api_key,
    'Content-Type: application/json'
));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array(
    'model' => 'gpt-3.5-turbo',
    'messages' => $_SESSION['chat_history'],
    'max_tokens' => 500
)));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code === 200) {
    $result = json_decode($response, true);
    $reply = $result['choices'][0]['message']['content'];
    $_SESSION['chat_history'][] = array('role' => 'assistant', 'content' => $reply);
    echo json_encode(['success' => true, 'reply' => $reply]);
} else {
    $error_data = json_decode($response, true);
    $error_msg = $error_data['error']['message'] ?? 'Unknown API error';
    echo json_encode(['success' => false, 'error' => $error_msg]);
}
?>

 

5단계. 주의사항과 흔한 실수
문제 원인 해결책
401 Unauthorized 에러 API 키가 잘못되거나 헤더 형식이 틀림 API 키를 다시 확인하고, Authorization 헤더를 "Bearer [키]" 형식으로 정확히 입력
429 Rate Limited 에러 짧은 시간에 너무 많은 요청을 보냄 요청 간격을 조절하거나, 유료 계정으로 업그레이드해 제한 해제
500 Internal Server Error OpenAI 서버 장애 재시도 로직을 구현하거나, 일시적 에러인 경우가 많으므로 나중에 다시 시도
타임아웃 에러 응답이 너무 오래 걸림 (큰 응답이거나 서버 부하) CURLOPT_TIMEOUT을 60 이상으로 증가시키거나, max_tokens를 줄여서 응답 길이 제한
대화 이력이 누적되어 비용 증가 모든 메시지를 계속 보내면, 토큰 수가 기하급수적으로 증가 오래된 메시지를 제거하거나, 요약 기능을 추가해 이력 크기를 제한

 

결론
OpenAI API는 ChatGPT의 강력한 기능을 직접 웹 서비스에 통합할 수 있게 해주는 핵심 도구다. 환경 변수로 API 키를 관리하고, 에러 처리를 철저히 하며, 대화 이력을 올바르게 유지하는 것이 안정적인 통합의 기본이다. 작은 최적화와 에러 처리가 모여서 사용자 경험과 운영 안정성을 크게 높인다는 점을 잊지 말자. 이 글의 코드 예제를 참고해 chat_api.php를 구현하고, 실제 프로젝트에 적용하면 ChatGPT와 대화하는 웹 애플리케이션을 완성할 수 있을 것이다.