spicyapi문서
SDK
본문

PHP SDK

SpicyAPI 공식 PHP SDK(spicyapi/spicyapi) 안내입니다. Composer 설치부터 견적과 첫 생성, 로컬 파일 업로드, 안전한 웹훅 검증, 모든 오류 코드 처리까지 다룹니다.

spicyapi/spicyapi는 SpicyAPI의 공식 PHP SDK입니다. 이미지·영상·오디오 생성을 SpicyClient 하나 뒤에 놓습니다. API 키를 대신 붙여 주고, 응답 봉투를 벗겨 평범한 연관 배열로 돌려주고, 상한이 있는 백오프로 작업을 기다리고, 로컬 파일을 한 번의 호출로 업로드하고, PHP 코드가 흔히 저지르는 세 가지 실수 없이 웹훅 서명을 검증합니다.

PHP 8.1 이상과 ext-json, ext-hash가 필요하며, 라이브러리 의존성은 하나도 없습니다. ext-curl은 있으면 쓰고, 없으면 https:// 스트림 래퍼로 물러납니다.

나에게 맞는 도구인가요?

하고 싶은 일쓸 도구
내 PHP 코드에서 SpicyAPI 호출하기이 SDK
Node.js나 TypeScript에서 호출하기TypeScript SDK
Python, Go, Java에서 호출하기Python, Go, Java SDK
코드를 짜지 않고 터미널에서 생성하기CLI
AI 어시스턴트가 대신 SpicyAPI를 호출하게 하기MCP 서버와 Agent Skill
채팅·텍스트 모델 쓰기OpenAI, Anthropic, Gemini 형식을 지원하는 기존 PHP 클라이언트. 텍스트와 스트리밍을 보세요

설치

composer require spicyapi/spicyapi

키는 환경 변수에 넣으세요. 그래야 코드에도, 버전 관리에도 키가 남지 않습니다.

export SPICY_API_KEY="sk-spicy-..."

첫 결과

가격을 묻고, 판단하고, 작업을 만들고, 기다리고, 파일을 읽습니다. 견적은 무료이며 아무것도 예약하지 않습니다.

use SpicyApi\SpicyClient;

$client = new SpicyClient();                 // 환경 변수에서 SPICY_API_KEY를 읽습니다

$quote = $client->quote('MODEL_ID_FROM_CATALOG', ['prompt' => 'a lantern in fog']);
printf("at most %s\n", $quote['maxCharge']); // 돈을 쓰기 전에 판단하세요

$task = $client->createTask(
    model: $quote['model'],
    input: ['prompt' => 'a lantern in fog'],
    idempotencyKey: SpicyClient::idempotencyKey(),
    quoteId: $quote['quoteId'],
    expectedCost: $quote['estimatedCost'],
);

$final = $client->waitForTerminal($task['taskId']);
foreach (SpicyClient::readyAssets($final) as $asset) {
    echo $asset['url'], "\n";
}

모든 호출은 응답 봉투의 data를 디코딩한 값을 평범한 연관 배열로 돌려줍니다. 그러니 $quote->maxCharge가 아니라 $quote['maxCharge']로 읽으세요.

MODEL_ID_FROM_CATALOG는 자리표시자입니다. 실제 ID는 인증된 카탈로그에서 가져오세요. $client->listModels()이고, HTTP로는 GET /api/v1/models입니다. 기억이나 오래된 페이지에서 가져오면 안 됩니다. 모델은 계속 추가되고, 멈추고, 퇴역합니다. 입력은 그 모델 자신의 inputSchema를 보고 구성하세요. listModels()가 돌려줍니다. 모델마다 받는 필드가 다릅니다.

금액은 부동소수점이 아니라 미국 달러를 적은 십진 문자열입니다. 이진 부동소수점은 대부분의 십진 금액을 정확히 담지 못하기 때문입니다. 비교는 SpicyClient::compareUsd()로 하세요. 이 API 어디에도 크레딧이나 포인트는 없습니다.

waitForTerminal은 어떤 종료 상태에서든 값을 돌려주며, 종료 상태라고 해서 성공한 것은 아닙니다. 출력을 읽기 전에 상태부터 확인하고, 파일이 아니라 텍스트로 답하는 엔드포인트라면 SpicyClient::outputText($final)를 먼저 읽으세요.

첫 폴링 건너뛰기

waitSeconds는 작업이 끝날 때까지 연결을 열어 두므로, 짧은 작업이라면 폴링 반복문이 아예 필요 없습니다. 다만 끝난 작업을 약속해 주지는 않습니다. 주어진 시간이 다하면 평소의 접수 응답이 대신 돌아옵니다. 모양은 같고 state만 다릅니다. 그러니 파라미터를 넘겼다는 사실이 아니라 언제나 상태를 보고 분기하세요.

$submitted = $client->createTask(
    model: 'MODEL_ID_FROM_CATALOG',
    input: ['prompt' => 'a lantern in fog'],
    idempotencyKey: SpicyClient::idempotencyKey(),
    waitSeconds: 30,
);

if (!SpicyClient::isTerminal($submitted)) {
    $submitted = $client->waitForTerminal($submitted['taskId']);
}

로컬 파일에서 시작하기

이미지-투-비디오, 피사체 교체, 이미지 편집은 모두 내 소재가 먼저 이쪽에 올라와 있어야 합니다. uploadFile은 티켓 발급, 스토리지로 PUT, 커밋이라는 세 단계 업로드를 한 번의 호출로 끝내고 spicy:// URI를 돌려줍니다. 입력에 넣는 값이 바로 그 URI입니다.

$uploaded = $client->uploadFile('/path/to/reference.png');
$task = $client->createTask(
    model: 'MODEL_ID_FROM_CATALOG',
    input: ['image' => $uploaded['uri'], 'prompt' => 'slow dolly in'],
    idempotencyKey: SpicyClient::idempotencyKey(),
);

공개적으로 접근할 수 있는 HTTPS 주소라면 업로드 없이 입력에 바로 넣어도 됩니다. 이미지는 10 MiB까지, 오디오와 영상은 90 MiB까지 받습니다. 미디어를 보세요.

웹훅

use SpicyApi\SpicyWebhook;

// 시크릿이 첫 번째 인자입니다. 나머지는 선택입니다. 프레임워크가 이미 가져가지 않았다면
// $_SERVER와 php://input의 원본 바이트를 알아서 읽습니다.
$delivery = SpicyWebhook::verifyRequest(getenv('SPICY_WEBHOOK_SECRET') ?: '');

// 본문을 버퍼링하는 프레임워크 안에서는 정확한 바이트를 직접 넘기세요.
$delivery = SpicyWebhook::verifyRequest(
    secret: getenv('SPICY_WEBHOOK_SECRET') ?: '',
    server: $_SERVER,
    rawBody: $request->getContent(),
);

PHP 코드가 이 부분에서 틀리는 방식이 세 가지 있고, 이 클래스는 셋 다 피합니다.

  • 검증 전에 본문을 다시 직렬화하기. json_decode 뒤에 json_encode를 거치면 바이트가 달라지고, 서명은 다시는 맞지 않습니다. 원본 바이트로 검증하세요.
  • 서명을 ==나 ===로 비교하기. 타이밍 문제는 차치하더라도, 둘 다 0e… 모양인 두 문자열에 ==를 쓰면 숫자로 비교합니다. "0e123" == "0e456"은 true입니다. 이 클래스는 hash_equals를 씁니다.
  • 타임스탬프 확인 건너뛰기. 유효한 서명은 영원히 유효한 채로 남습니다. 신선도 검사 창이 없으면, 가로챈 전송을 그대로 재생할 수 있습니다.

전송은 재시도되므로 전송 ID로 중복을 걸러 내세요. 전체 전송 형식과 재시도 일정은 웹훅에 있습니다.

잔액, 사용량, 기록

$balance = $client->getBalance();                        // available / held / total, 달러 문자열
$usage = $client->getUsage('2026-09-01', '2026-09-08');  // [from, to) 구간의 정산된 지출

foreach ($client->iterTasks(state: 'succeeded') as $summary) {
    echo $summary['taskId'], "\n";                       // 모든 페이지, 필터는 알아서 그대로 반복
}

held는 아직 정산되지 않은 작업이 예약해 둔 돈이라서, 쓴 것도 아니고 쓸 수 있는 것도 아닙니다. 날짜는 YYYY-MM-DD 형식의 UTC 달력 날짜이며, 최대 92일까지의 반열린 구간 [from, to)를 이룹니다. 세 호출 모두 호출한 그 API 키만 봅니다. 같은 계정의 다른 키와 콘솔에서 만든 생성은 포함되지 않습니다.

getUsage()는 장부를 맞춰 보기 위한 것이지 진행 상황을 확인하기 위한 것이 아닙니다. 모든 키가 함께 쓰는 계정 단위 요청 예산이 걸려 있고, 그 제한기는 막히는 쪽으로 실패합니다. 이걸 폴링하면 계정 전체가 자기 사용량 조회조차 못 하게 될 수 있습니다. 작업을 따라가려면 getTask()를 쓰세요.

listTasks()는 한 페이지를 {items, hasMore, nextCursor}로 돌려줍니다. 직접 페이지를 넘길 때는 모든 필터를 하나도 바꾸지 않고 그대로 반복하면서 커서만 옮기고, hasMore가 false이거나 nextCursor가 비었을 때 멈춰야 합니다. hasMore만 확인하면, 서버 쪽의 사소한 오작동이 돈이 드는 호출의 끝없는 반복으로 바뀌고 겉보기에는 멈춰 있는 것처럼 보입니다. iterTasks()는 이 모든 것을 대신해 줍니다.

오류

실패는 SpicyApiError를 던지며, 여기에 $businessCode, $requestId, $retryAfterSeconds가 담깁니다. 분기는 언제나 비즈니스 코드로 하고, 메시지로는 절대 하지 마세요. 메시지는 번역되지만 코드는 번역되지 않습니다.

HTTP 503은 서로 다른 비즈니스 코드 세 개가 함께 씁니다. 그래서 상태만으로는 무엇을 해야 할지 알 수 없습니다.

코드의미대응
40003업로드한 바이트가 티켓과 맞지 않습니다새 티켓을 받아 처음부터 다시 하세요
40004그 파라미터 조합을 처리할 배포가 없습니다메시지에 이름이 나온 파라미터를 바꾸세요
40201잔액이 모자랍니다충전하세요
409같은 멱등 키가 다른 요청에 다시 쓰였습니다그 키로는 원래 요청을 다시 보내고, 바뀐 요청은 새 키로 보내세요
40901작업이 만들어지기 전에 가격이 바뀌었습니다견적을 다시 받되 멱등 키는 그대로 두세요
503의존하는 구성 요소를 잠시 쓸 수 없습니다Retry-After만큼 기다린 뒤 다시 시도하세요
50301지금 쓸 수 있는 배포나 가격이 없습니다카탈로그를 새로 읽으세요
50302동기 생성이 상위에서 실패했고 환불됐습니다새 멱등 키로 다시 시도하세요

$error->guidance()는 실행 중에도 같은 조언을 돌려줍니다. 모든 코드는 오류에 정리돼 있습니다.

돈을 아껴 주는 두 가지

제출 하나에 멱등 키 하나를 유지하고, 다시 보낼 때마다 그 키를 그대로 다시 쓰세요. 응답을 받지 못했다는 사실이 작업이 만들어지지 않았다는 증거는 아닙니다. 멱등성을 보세요.

성공한 작업은 과금됩니다. 결과가 마음에 들지 않아도 그렇습니다. quote()는 아무것도 예약하지 않고 아무것도 만들지 않습니다.

이 패키지가 하지 않는 일

  • 텍스트 모델. 텍스트 모델은 OpenAI, Anthropic, Gemini의 통신 형식을 씁니다. 그 형식을 지원하는 기존 PHP 클라이언트가 https://api.spicyapi.ai/v1에 대고 이미 그대로 작동합니다. 텍스트와 스트리밍을 보세요.
  • 브라우저 쪽 코드. 키는 내 서버에 있어야 합니다. 방문자에게 전달되는 어떤 것에도 넣지 마세요.
  • 모델 고르기와 필드 채우기. SDK는 카탈로그와 모델별 스키마를 건네줄 뿐이고, 무엇을 보낼지는 직접 정합니다.
  • 작업 취소. 작업이 접수되면 끝까지 실행됩니다.

소스

이 패키지는 MIT 라이선스이고 **github.com/spicyapi-ai/spicy-php**에서 공개적으로 개발됩니다. 저장소의 README는 이 클라이언트를 가장 짧게 한 바퀴 둘러보는 문서이고, examples/에는 바로 실행할 수 있는 스크립트가 있습니다. 이 페이지는 그 주변 개념을 채워 줍니다.

다음 단계

이 페이지에서