가이드: 이미지 생성과 편집
이미지 엔드포인트라는 부류의 공통 규칙 — 두 가지 요청 형태, 출력 크기를 적는 세 가지 방식, 참조 이미지가 청구서에 더하는 것, 어댑터 가중치, 그리고 거부 메시지 읽는 법.
이 가이드는 특정 모델이 아니라 이미지 엔드포인트라는 부류를 다룹니다. 모든 엔드포인트에서 똑같이 유지되는 부분과, 더 쓸모 있게는 한 엔드포인트를 위해 쓴 요청 본문이 다른 엔드포인트에서는 통하지 않게 만드는 차이들을 설명합니다.
파라미터 표는 일부러 싣지 않았습니다. 파라미터의 출처는 단 하나, 호출하려는 모델에 대해 라이브 카탈로그가 돌려주는 inputSchema뿐입니다. 이 페이지는 그 스키마를 읽는 법과, 그 안에 드러나 있지 않은 것을 다룹니다.
| 알고 싶은 것 | 대신 읽을 문서 |
|---|---|
| 키와 잔액에서 시작해 첫 이미지까지 처음부터 끝까지 | 빠른 시작 |
| 같은 여정을 영상으로, 단계별로 | 튜토리얼: 사진을 영상으로 바꾸기 |
| 특정 모델의 모든 필드 | 그 모델의 inputSchema — 모델 카탈로그와 스키마 참고 |
| 한 사진의 얼굴이나 머리를 다른 사진으로 옮기기 | 피사체 교체 |
MODEL_ID_FROM_CATALOG는 자리표시자입니다
아래 예시는 실제 모델 ID가 들어갈 자리를 모두 MODEL_ID_FROM_CATALOG로 적었습니다. 이 ID는 인증된 라이브 카탈로그(GET /api/v1/models?modality=image)에서 가져오고, 비용이 드는 작업 전에 enabled: true와 available: true를 모두 확인하세요. 카탈로그 조회는 무료입니다. 자리표시자를 그대로 보내면 "not found"만 돌아옵니다.
두 가지 요청 형태
이미지 작업은 두 종류의 태스크로 도착하고, 카탈로그에서도 나뉘어 있습니다.
- 생성 엔드포인트는
/text-to-image로 끝납니다. 말을 보내면 그림이 돌아옵니다. - 편집 엔드포인트는
/edit로 끝납니다. 말과 함께 사진을 최소 한 장 보냅니다.
편집에는 표기가 두 가지 있고, 카탈로그를 필터링하기 전에 둘 다 알아 두는 편이 좋습니다. 엔드포인트는 edit 접미사로 공개되지만, tasks 배열은 이를 image-to-image로 분류합니다. task 쿼리 파라미터는 둘 다 받습니다. 모델 카탈로그와 스키마를 참고하세요.
두 형태 모두 생성 엔벨로프, 태스크 생명주기, 결과 형식이 다른 미디어 모델과 완전히 같습니다. 달라지는 것은 input 안쪽뿐입니다.
prompt는 두 형태 모두에서 필수
편집 엔드포인트라면 사진만으로 충분할 테니 prompt는 선택이겠거니 하는 생각이 들기 쉽습니다. 프롬프트를 받는 엔드포인트라면 그렇지 않습니다. prompt는 두 형태 모두 루트 required 목록에 있고, 빼먹으면 과금이 일어나기 전에 거부됩니다.
{
"model": "MODEL_ID_FROM_CATALOG",
"input": {
"image_urls": ["spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3"]
}
}prompt: is required사진은 어디서 출발하는지를, 프롬프트는 그것으로 무엇을 할지를 말합니다. 지시를 받지 못한 편집 엔드포인트에는 작용할 대상이 없습니다.
예외가 하나 있는데, 흩어진 엔드포인트 하나가 아니라 한 계열 전체입니다. 동작 자체가 엔드포인트 쪽에 고정되어 있어서 prompt를 아예 받지 않고 사진만 받는 편집 엔드포인트가 있습니다. 이들은 피사체 교체 툴킷에 속하며 카탈로그에서 toolkit 필드를 가지므로 구분할 수 있습니다. 피사체 교체를 보세요. 이들에서 prompt: is not a supported field가 나오는 것은 버그가 아니라 올바른 응답입니다.
입력 이미지 필드의 이름은 세 가지
어느 것을 쓰는지는 그 엔드포인트 스키마의 일부이지, 태스크 종류에서 추론할 수 있는 것이 아닙니다.
| 필드 | 형태 | 보통의 의미 |
|---|---|---|
image_url | 이미지 참조 하나 | 피사체 사진이 정확히 한 장이고, 두 번째라는 개념이 엔드포인트에 없음 |
image_urls | 이미지 참조 배열 | 편집하거나 합치는 재료 그 자체. 지시가 가리키는 순서대로 나열 |
reference_image_urls | 이미지 참조 배열 | 원래는 글자만으로 하는 생성을 사진이 이끎. 피사체가 아니라 길잡이 |
놀라게 하는 쪽은 reference_image_urls입니다. 이것은 생성 엔드포인트에 나타나므로, /text-to-image 엔드포인트도 사진을 받을 수 있습니다. "text-to-image니까 요청에 이미지는 없다"고 단정하지 마세요.
이미지 참조란 공개 HTTPS URL, 업로드해 커밋한 spicy:// URI, 또는 작은 data:image/…;base64,… URI를 말합니다. 업로드는 미디어를, 표준 필드 이름은 Spicy Schema 계약을 보세요.
배열 한계는 짐작보다 훨씬 들쭉날쭉합니다
위 배열들은 minItems와 maxItems를 지니며 서버가 둘 다 강제합니다. 그리고 그 값은 엔드포인트마다 다릅니다. 지금 카탈로그에는 정확히 한 항목만 받는 배열부터 열 개까지 받는 배열까지 있습니다. 같은 계열의 다른 엔드포인트가, 같은 필드 이름으로, 다른 상한을 가질 수 있습니다.
image_urls: must contain at least 1 items
image_urls: must contain at most 3 itemsminItems와 maxItems는 호출하려는 모델의 스키마에서 읽으세요. 배열을 맨 array로만 적고 항목의 형태도 길이 한계도 보여주지 않는 파라미터 표는, 읽는 사람이 유일하게 행동으로 옮길 수 없는 줄이 됩니다. 이 사이트에 그런 표가 없는 이유가 바로 그것입니다.
출력 크기를 적는 방식은 세 가지
기존 요청 본문을 두 번째 엔드포인트에 겨눌 때 가장 먼저 부딪히는 것이 이것입니다.
이미지 카탈로그 전체에서 세 가지 표기가 쓰이고 있으며, 각 엔드포인트는 그중 정확히 하나만 받습니다.
| 표기 | 필드 | 비고 |
|---|---|---|
| 분리 | aspect_ratio 와 resolution | 화면 모양과 픽셀 등급을 따로 고름 |
| 프리셋 | size | enum 값 하나가 둘을 한 번에 고정 |
| 명시 | width 와 height | 픽셀 정수. 각각 자기 minimum과 maximum을 가짐 |
새 엔드포인트에서는 분리형이 가장 흔하고, 셋 중 쓸모도 가장 좋습니다. aspect_ratio는 생성 뒤에 자르는 것이 아니라 생성 전에 화면을 구성하므로, 픽셀 예산을 바꾸지 않고 모양만 바꿀 수 있습니다.
다른 표기의 필드는 무시가 아니라 거부됩니다
모든 이미지 inputSchema는 additionalProperties: false입니다. 엔드포인트가 나열하지 않은 필드는 거부되며, 이는 다른 엔드포인트에서 가져온 요청 본문이 조용히 기본값으로 떨어지는 대신 그대로 실패한다는 뜻입니다.
실패는 양방향으로 일어나고, 메시지는 필드를 지목합니다.
size: is not a supported field
aspect_ratio: is not a supported field둘 다 과금되지 않습니다. 비용은 돈이 아니라 가정 쪽입니다. input 객체 하나를 써서 여러 이미지 엔드포인트에 겨눌 수는 없고, 모두가 받아 주는 "호환" 표기도 없습니다.
어느 쪽이 가격 등급을 고르나
분리형 엔드포인트에서 두 필드는 보통 청구서에서 서로 다른 역할을 합니다.
resolution은 보통 가격 등급을 고릅니다. 견적에variant로 나타나고, 바꾸면 금액이 움직입니다.aspect_ratio는 보통 고르지 않습니다. 구도를 바꾸는 것은 크기를 바꾸는 것이 아니기 때문입니다.
저 두 문장의 "보통"은 실제로 일을 하고 있습니다. 기계가 읽을 수 있는 답은 필드 자신에 있습니다. x-ui.affects_price: true와, 엔드포인트가 선언한 경우 x-pricing.variantFields에 들어 있는지입니다. 습관이 아니라 스키마를 확인하세요. 가격 읽기와 주석 참조인 Spicy Schema 계약을 보세요.
허용되는 값도 엔드포인트마다입니다. 거부 메시지가 그것을 나열하므로 무료로 확인할 수 있습니다.
aspect_ratio: must be one of: "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "21:9"탐색할 때는 편리하지만 enum을 읽는 일을 대신하지는 못합니다. 스키마에는 필드를 뺐을 때 적용되는 default도 들어 있기 때문입니다.
참조 이미지가 청구서에 더하는 것
이것은 조용히 작용하는 쪽이고, 응답의 어느 곳도 그것을 가리키지 않기 때문에 절을 따로 둘 값어치가 있습니다.
미디어 모델의 보통 산수는 가격 × 수량이고, 수량은 입력 안 quantityField의 값(비어 있으면 1 단위)입니다. 이미지 엔드포인트에서 quantityField는 대개 num_outputs이고, 기대한 대로 움직입니다. 네 장을 달라고 하면 견적의 quantity가 4로 읽힙니다. 이 축은 정직합니다. 움직이는 것이 보입니다.
일부 엔드포인트는 그 위에 입력 사진 한 장마다 별도 요금을 더합니다. 그리고 이쪽은 움직이는 것이 보이지 않습니다. 같은 엔드포인트를 참조 한 장으로 한 번, 배열을 꽉 채워 한 번 견적 내 보면 이렇습니다.
unit이 같고,quantity도 같고,variant도 같은데,- 금액만 다릅니다.
견적을 세울 때 보통 쓰는 네 필드 중 셋이 제자리에 그대로 있습니다. 가격 × 수량으로 계산한 추정치는 실제보다 적게 나오고, 응답 어디에도 그 표시가 없습니다.
기계가 읽을 수 있는 특징
x-ui.affects_price: true가 붙어 있으면서 quantityField도 아니고 x-pricing.variantFields에도 없는 필드는, 등급을 고르지 않은 채 금액만 움직입니다. 그런 엔드포인트에서는 카탈로그 pricing을 하한으로 읽고, 금액은 견적에서 가져오세요.
이는 모델 카탈로그와 스키마에 적힌 것과 같은 규칙이며, 이미지 편집이 이 규칙을 가장 자주 만나는 자리입니다. 입력 사진 배열은 이 표시를 자주 답니다.
얼마나 더해지는지는 엔드포인트마다 다르고 정해진 비율도 없습니다. 지금 카탈로그에는 배열을 채우면 참조 한 장의 몇 배가 되는 엔드포인트도, 추가 참조가 전혀 무료인 엔드포인트도 있으며, 물어보는 것 말고는 구분할 방법이 없습니다. 그러니 같은 계열의 다른 엔드포인트로 배열 값을 미루어 짐작하지 말고, 참조 한 장짜리 견적으로 가득 찬 배열의 값을 계산하지도 마세요.
실제로 보낼 본문으로 견적 내기
curl -sS https://api.spicyapi.ai/api/v1/jobs/quote \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"MODEL_ID_FROM_CATALOG","input":{"prompt":"your instruction","image_urls":["spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3"]}}'견적은 태스크 생성과 똑같은 검증을 하지만 비용이 들지 않고, 아무것도 시작하지 않으며, 아무것도 묶어 두지 않습니다. 그 본문에 대해 estimatedCost, maxCharge, quantity, unit을 돌려줍니다. 배열을 바꿨으면 다시 견적 내세요. 자세한 내용과 응답을 놓쳤을 때의 복구는 견적과 호환성에, 실제 연동에서 견적을 어디에 두어야 하는지는 프로덕션 연동에 있습니다.
어댑터 가중치
일부 엔드포인트는 기본 모델 위에 직접 파인튜닝한 가중치를 얹게 해 줍니다. 표준 필드는 loras이고, 단계를 따로 노출하는 엔드포인트에서는 high_noise_loras와 low_noise_loras입니다.
객체의 배열, 안쪽에도 자체 규칙
이 필드들은 x-ui.widget: "object-list"를 답니다. items가 그 자체로 완결된 객체 스키마인 배열입니다. 자체 required, 자체 additionalProperties: false, 그리고 하위 필드마다 자기 타입·범위·default를 가집니다. 전형적인 항목 하나는 가중치 한 벌과 그것을 얼마나 세게 적용할지를 지정합니다.
{
"model": "MODEL_ID_FROM_CATALOG",
"input": {
"prompt": "your scene",
"loras": [
{ "path": "https://weights.example.com/style.safetensors", "scale": 1 },
{ "path": "https://weights.example.com/character.safetensors", "scale": 0.6 }
]
}
}항목의 형태는 엔드포인트마다 고정되어 있지 않습니다. 가중치 파일로 무엇을 받는지(직접 주소인지, 내장 라이브러리의 키인지)는 그 엔드포인트 자신의 items 하위 스키마가 좁힙니다. 하위 필드 이름, 몇 개까지 들어가는지, 어느 것이 필수인지도 마찬가지입니다. 이 예시가 아니라 items를 읽으세요. 위젯 설명은 Spicy Schema 계약에, 그것을 중첩 표로 만드는 법은 모델 카탈로그와 스키마에 있습니다.
이 항목 안쪽 규칙 하나는 오해하기 쉽습니다. 기본값은 배열 항목 안까지 미칩니다. items.required에 있으면서 default도 가진 하위 필드는 사실상 선택입니다. 빼면 검증 전에 기본값이 채워집니다. 기본값이 없는 하위 필드는 빠지면 거부됩니다. 즉 items.required는 "내가 보내는 JSON에 반드시 있어야 한다"는 뜻이 아닙니다.
가중치를 가져오는 방법
path에는 다음 세 가지 중 하나를 넣을 수 있습니다.
- 공개 링크.
.safetensors파일로 가는 링크입니다. 엔드포인트가 받는 경우에는owner/repo형태의 Hugging Face 저장소 ID도 쓸 수 있습니다. 모델이 생성 시점에 내려받으므로, 새 가중치를 쓰는 첫 호출은 그 뒤의 호출보다 눈에 띄게 느립니다. - 업로드. 직접 업로드 흐름으로 파일을
application/octet-stream으로 최대 2 GiB까지 업로드하고, 돌려받은spicy://f/…URI를 사용합니다. 커밋 시점에 완전한 safetensors 파일인지 확인합니다. - 가져오기. 로그인이 필요한 파일(비공개 또는 게이트가 걸린 Hugging Face 저장소의 파일, API 키가 필요한 Civitai 파일 등)은 링크와 액세스 토큰을
POST /api/v1/files/import로 보냅니다. SpicyAPI가 파일을 한 번 내려받아spicy://f/…URI를 돌려줍니다. 토큰은 그 다운로드에만 쓰이고 저장되지 않습니다.
curl -sS https://api.spicyapi.ai/api/v1/files/import \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
--data "$(jq -n --arg url "https://huggingface.co/OWNER/REPO/blob/main/your_lora.safetensors" --arg token "$HF_TOKEN" '{url: $url, token: $token}')"저장소에 .safetensors 파일이 정확히 하나만 있다면 url에 저장소 ID(OWNER/REPO)나 저장소 페이지를 넣어도 됩니다. 응답 형태는 커밋과 같으며, data.uri를 loras[].path에 넣으세요. 업로드하거나 가져온 가중치는 다른 업로드와 마찬가지로 하루 동안 보관되고 다른 계정과 공유되지 않습니다. 그 뒤에는 다시 업로드하거나 가져오세요.
제출 시점에 확인하는 것은 링크이지 그 안의 가중치가 아닙니다
태스크를 만들기 전에 각 가중치 링크를 확인합니다. 로그인이 필요한 링크, 404를 돌려주는 링크, 웹 페이지가 열리는 링크는 400으로 거부되며 과금되지 않고, 메시지에 해당 항목이 표시됩니다. 그 밖의 링크는 통과하므로, 나중에 호스트가 내려가거나 가중치 로드에 실패하면 태스크 실패로 돌아옵니다.
이 확인으로 알 수 없는 문제는 나중에 태스크 실패로 돌아옵니다. errorCode는 플랫폼의 공개된 아홉 부류 중 하나이고, 메시지에 무슨 일이 있었는지가 설명되며, 그 태스크를 위해 잡아 둔 금액은 돌려드립니다. 오류를 보세요.
최악은 더 조용합니다. 다른 기본 모델용으로 만든 가중치는 실패 없이 로드되고도 출력을 거의 바꾸지 않을 수 있습니다. 읽을 오류는 없고, 기본 모델처럼 보이는 그림만 남습니다.
그래서 배포 시점에 확인하세요
확인을 요청 경로에서 빼내 출시할 때 도는 곳으로 옮기세요.
- 도달 가능성. 프로덕션에 넣을 모든 가중치 주소에
HEAD요청을 보냅니다. 플랫폼도 태스크를 만들 때 각 링크를 확인하지만, 오늘 되는 링크가 다음 주에 안 될 수 있고, 배포 시점의 확인은 트래픽보다 먼저 그것을 잡아냅니다. - 효과. 새 가중치 파일마다 한 번 생성해, 같은 프롬프트와 같은 시드로 그것 없이 만든 결과와 비교합니다. 둘을 구분할 수 없다면 그 파일은 생각대로 일하고 있지 않습니다. 배치를 맡기기 전에 확인하세요.
- 안정성. 주소는 프로덕션에 있는 동안 계속 작동해야 합니다. 사라진 가중치 파일은 원인이 뚜렷하지 않은 태스크 실패로 바뀌고, 그것은 원인으로 지목될 배포로부터 며칠 뒤에 일어납니다.
이 필드들에 대한 엔드포인트별 조언 — 어떤 값이 무엇에 견주어 측정되는지, 어떤 설정이 눈에 띄게 느려지는지, 스키마는 받지만 후회하게 될 입력은 무엇인지 — 은 공개 카탈로그의 paramNotes에 있습니다. 파라미터 이름을 키로 삼고 문서의 열 개 언어 모두로 제공됩니다. 그것은 산문이지 규칙이 아닙니다. 무엇이 받아들여지는지는 여전히 inputSchema가 정합니다. 모델 카탈로그와 스키마를 보세요.
다른 미디어 모델과 똑같이 비동기
이미지 엔드포인트는 요청-응답 호출이 아니라 태스크입니다. 태스크를 만들고 폴링이나 웹훅으로 결과를 거둡니다. queued와 running은 비종료, succeeded·failed·expired가 종료 상태입니다. 상태에 관한 정본은 비동기 태스크입니다.
이미지는 영상보다 빨리 끝나서 동기 호출처럼 쓰고 싶어집니다. 참으세요. 여기서는 짧은 대기가 동기처럼 느껴지지만, 대기 예산이 떨어진 시점에 아직 돌고 있는 태스크를 코드가 다뤄야 하고, 그것을 웹훅에 넘기는 편이 요청을 열어 둔 채 붙잡는 것보다 잘 확장됩니다. 폴링은 작업 제출과 독립된 계정 읽기 허용량을 사용합니다. 너무 자주 폴링하면 상태 조회가 호출 제한에 걸릴 수 있습니다. 호출 제한을 참고하세요.
결과 읽기
성공한 태스크는 output.assets[]에 파일을 나열합니다. 이미지 엔드포인트에서 이 목록에 대해 세 가지.
- 파일이 하나 이상일 수 있습니다.
num_outputs가 1보다 크면 사진 한 장마다 에셋 하나가 옵니다. 처음부터 반복문으로 쓰세요.assets[0]만 읽는 핸들러는 값을 치른 나머지를 조용히 버립니다. - 확장자를 정하는 것은
mime이지 엔드포인트 이름도 프롬프트도 아닙니다. 엔드포인트가output_format을 내놓는 경우 그 필드가 인코딩을 고릅니다. 내놓지 않는 경우 인코딩은 그 엔드포인트 고유이고, 무엇을 받았는지 알 길은mime뿐입니다. - 아직 복사 중인 파일은
pending: true이고url이 없습니다. 결과는 건네지기 전에 SpicyAPI 자체 저장소로 복사되며, 태스크가 성공을 보고한 뒤 몇 초가 걸립니다. 링크가 없는 것을 실패로 다루지 말고 태스크를 다시 읽으세요.
다운로드 링크는 약 20분 동안 유효하고 API 키가 필요 없습니다. 새 링크가 필요하면 태스크를 다시 읽으세요. 생성된 파일은 최대 14일 보관되며, 계정의 보관 설정이 더 짧으면 그쪽을 따릅니다. 결과와 다운로드와 보관과 파기를 보세요.
거부 메시지 읽기
거부된 요청은 비용도 들지 않고 아무것도 만들지 않으므로, 견적 엔드포인트는 곧 보낼 본문에 대한 무료 검증기 노릇도 합니다.
첫 번째 하나만이 아니라 모든 문제가 보고됩니다. 메시지는 ; 로 이어지므로, 한 번 거부당하는 것만으로 본문의 잘못을 전부 알 수 있습니다.
prompt: is required; loras.0.path: is required; loras.0.scale: must be at most 4; bogus: is not a supported field배열 안쪽의 문제는 그 항목을 가리킵니다. 포인터는 필드.색인.하위필드 꼴이고 색인은 0부터입니다.
loras.1.scale: must be at most 4이 포인터는 고칠 사람에게 그대로 넘기세요. 항목 여러 개가 동시에 오가는 상황에서, 어느 것이 범위를 벗어났는지 아는 것은 한 줄 수정과 이분 탐색의 차이입니다.
만나게 될 형태는 이렇습니다.
| 메시지 | 뜻 | 대처 |
|---|---|---|
X: is required | X가 루트 required에 있음 | 추가한다. 편집 엔드포인트에서는 대개 prompt나 사진 필드 |
X: is not a supported field | X가 properties에 없음. 모르는 필드는 무시가 아니라 거부 | 빼거나 이 엔드포인트의 표기에 맞춘다 — 출력 크기 참고 |
X: must be one of: … | 값이 enum 바깥 | 메시지가 허용값을 나열한다. enum과 default는 스키마에 있다 |
X: must contain at least N items | minItems 미만 | 엔드포인트가 요구하는 배열은 비워서 보낼 수 없다 |
X: must contain at most N items | maxItems 초과 | 이 엔드포인트의 상한을 읽는다. 모든 엔드포인트가 같지 않다 |
X: must be an array | 값이 선언된 타입이 아님 | 단일 항목을 리스트로 감싼다 |
X: must be at most N characters | maxLength 초과 | 프롬프트에도 엔드포인트별 상한이 있다 |
X.<n>.<sub>: is required | 항목 <n>의 하위 필드가 필수이고 기본값이 없음 | 그 항목에만 추가한다 |
X.<n>.<sub>: is not a supported field | 항목 스키마가 additionalProperties: false | 그 키를 그 항목에서 뺀다 |
X.<n>.<sub>: must be at most N | 하위 필드가 자기 범위 바깥 | 범위 안으로 맞춘다. 범위는 배열 단위가 아니라 하위 필드 단위 |
통과할 줄 알았던 요청이 거부되면 대개 스키마가 낡은 것이 원인입니다. 400 뒤에는 카탈로그를 다시 읽고, 영구 스냅숏은 절대 두지 마세요. 캐시한 사본이 얼마나 유효한지는 카탈로그 캐싱에 있습니다. 나머지는 문제 해결이 다룹니다.

