v1
웹에서 써보기 빠른 시작

뉴스카드 생성 API

링크 하나를 보내면 인스타그램용 카드 이미지(1080×1350)와 캡션을 만들어 돌려줍니다. 브라우저 없이 서버가 직접 렌더링하므로 텔레그램 봇·자동화 에이전트 등 어디서든 호출할 수 있습니다.

Base URLhttps://algoajae.info
인증X-API-Key 헤더
응답JSON (이미지는 URL로 제공)
이미지 규격1080 × 1350 PNG (인스타 4:5)
글자가 깨지지 않습니다. AI는 글자 없는 배경만 만들고, 한글은 서버가 Paperlogy 폰트로 직접 렌더링합니다. 덕분에 오타가 생기지 않고, 헤드라인을 바꿔도 이미지를 다시 만들 필요가 없어 추가 비용이 0원입니다.

빠른 시작

curl -X POST https://algoajae.info/api/cards \
  -H "X-API-Key: $CARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://n.news.naver.com/mnews/article/654/0000192269"}'
{
  "cardId": "7dc12ecf2080",
  "imageUrl": "https://algoajae.info/api/cards/7dc12ecf2080/image.png?v=0",
  "selected": 0,
  "line1": "레버리지 ETF 규제",
  "line2": "거래대금 30% 급감했다",
  "variants": [
    { "line1": "레버리지 ETF 규제", "line2": "거래대금 30% 급감했다", "angle": "숫자강조" },
    { "line1": "31일부터 예탁금 3배", "line2": "투자자들 발걸음 멈췄다", "angle": "반전" }
    // ... 총 10개
  ],
  "caption": "오는 31일부터 레버리지 ETF 규제가 강화됩니다.\n기본예탁금이...",
  "hashtags": ["레버리지ETF", "투자규제", "금융시장"],
  "hashtagText": "#레버리지ETF #투자규제 #금융시장",
  "source": { "adapter": "naver-news", "title": "...", "chars": 1265 },
  "cost": { "usd": 0.0583, "krw": 77 },
  "expiresAt": "2026-07-28T12:00:00.000Z"
}
imageUrl은 공개 URL입니다. 텔레그램 sendPhoto에 그대로 넘기면 됩니다 — 파일 업로드가 필요 없습니다.

인증 · 비용 부담

모든 요청에 X-API-Key 헤더가 필요합니다. 사이트 로그인 토큰(Authorization: Bearer <JWT>)으로도 호출할 수 있습니다.

X-API-Key: your-api-key-here

비용은 누구에게 청구되나

OpenAI·Apify 키는 아래 순서로 결정되며, 그 키의 소유자에게 사용료가 청구됩니다.

순서출처청구 대상
1요청 헤더 X-OpenAI-Key / X-Apify-Token헤더에 담은 키의 주인
2회원이 내 정보에 저장한 키 (JWT로 호출 시)그 회원
3서버 환경변수사이트 운영자
회원이 각자 자기 키로 쓰게 하려면 내 정보에 키를 저장한 뒤 본인 로그인 토큰으로 호출하세요.

API 키 발급받기

누구나 발급받을 수 있습니다. 사이트 로그인 → 내 정보 → 맨 아래 카드 API 키발급 버튼. 관리자에게 요청할 필요가 없습니다.
  • 만료되지 않습니다. 로그인 토큰(JWT)은 30일이면 끊기지만 이 키는 폐기할 때까지 유효합니다
  • 사용료는 본인이 내 정보에 저장한 OpenAI 키로 청구됩니다 — 다른 회원과 완전히 분리됩니다
  • 발급 직후 한 번만 보여줍니다. 서버에는 해시만 저장되어 다시 확인할 수 없습니다
  • 분실하면 재발급하세요. 이전 키는 즉시 무효가 됩니다
  • 유출이 의심되면 폐기 버튼으로 즉시 차단할 수 있습니다

발급 전에 내 정보 → OpenAI API Key를 먼저 저장해 두세요. 없으면 카드 생성이 막힙니다.

이미지 조회(GET .../image.png)만 인증이 없습니다. URL을 아는 사람은 볼 수 있으므로 공개해도 되는 내용만 담으세요.

카드 만들기

POST/api/cards

요청 본문

필드타입기본값설명
urlstring기사·게시글 링크. text둘 중 하나 필수
textstring링크 대신 본문을 직접 전달 (사이트가 차단될 때)
noAIbooleanfalsetrue면 AI 배경 생략 — 6배 저렴, 10배 빠름
imageUrlstring이 이미지를 배경으로 사용 (AI 생성 안 함)
imageBase64string배경 이미지를 base64로 직접 전달
variantnumber0처음부터 N번 헤드라인으로 렌더
sizestring1088x1360배경 해상도. 1728x2160도 가능
qualitystringmediumlow / medium / high
asyncbooleanfalsetrue면 즉시 작업ID 반환
webhookUrlstringasync일 때 완료 결과를 POST로 전송
styleobject디자인 설정
copyPromptstring카피 지침 덮어쓰기
imageStylestring이미지 스타일 추가
tonestringphotorealphotoreal / illust

동기 모드 (기본)

응답이 올 때까지 기다립니다. AI 배경 포함 시 약 60~90초가 걸리므로 클라이언트 타임아웃을 넉넉히 잡으세요.

curl -X POST https://algoajae.info/api/cards \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://...", "noAI": true}'   # 약 6초

비동기 모드

curl -X POST https://algoajae.info/api/cards \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://...", "async": true, "webhookUrl": "https://my-bot/hook"}'

즉시 202로 응답합니다:

{
  "jobId": "j_601a22b8cb71",
  "status": "queued",
  "statusUrl": "https://algoajae.info/api/cards/j_601a22b8cb71"
}

완료되면 webhookUrl로 결과가 POST됩니다. 웹훅을 쓰지 않으면 statusUrl을 폴링하세요.

진행 단계: fetchingcomposinggenerating_imagerenderingdone

상태 · 정보 조회

GET/api/cards/:id

cardIdjobId 모두 조회할 수 있습니다.

curl https://algoajae.info/api/cards/j_601a22b8cb71 -H "X-API-Key: $KEY"

# 진행 중
{ "jobId": "j_601a22b8cb71", "status": "running", "stage": "generating_image" }

완료 시에는 카드 정보 전체가 함께 반환됩니다.

헤드라인 교체

POST/api/cards/:id/variant비용 0원

10개 후보 중 다른 것으로 다시 렌더합니다. 배경을 재사용하므로 추가 비용이 없고 즉시 완료됩니다.

curl -X POST https://algoajae.info/api/cards/7dc12ecf2080/variant \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"index": 4}'

응답 형식은 POST /api/cards와 같으며 imageUrl이 새 헤드라인 버전을 가리킵니다.

배경 붙이기 · 교체

POST/api/cards/:id/background

noAI: true카피만 먼저 뽑아 헤드라인을 확인한 뒤, 마음에 들 때만 배경을 만드는 흐름에 씁니다. 카피는 이미 있으므로 카피 비용이 다시 들지 않습니다.

# ① 카피만 — 6초, 17원
POST /api/cards
{ "url": "https://...", "noAI": true }

# ② 헤드라인이 마음에 들면 배경 추가 — 48초, +65원
POST /api/cards/<cardId>/background
{}                                  # AI로 생성
{ "imageUrl": "https://..." }        # 또는 내 이미지로
{ "imageBase64": "..." }             # 또는 직접 업로드
{ "prompt": "다른 장면 묘사" }         # 프롬프트를 바꿔 재생성
배경을 바꾸면 imageUrl&r=1 같은 버전이 붙습니다. 같은 URL이면 텔레그램·브라우저가 옛 이미지를 캐시해서 보여주기 때문입니다. 반환된 URL을 그대로 쓰세요.

이미지

GET/api/cards/:id/image.png인증 불필요
GET /api/cards/7dc12ecf2080/image.png       # 현재 선택된 헤드라인
GET /api/cards/7dc12ecf2080/image.png?v=4   # 4번 후보 버전
보관 기간 6시간. 이후 자동 삭제되며 서버 재배포 시에도 사라집니다. 오래 보관하려면 받은 즉시 내려받아 두세요.

비용

cost 필드에 실제 사용한 토큰 기준 금액이 담깁니다.

모드소요 시간비용
noAI: true (카피만)약 6초약 13원
AI 배경 포함약 60~90초약 77원
헤드라인 교체즉시0원

인스타그램 링크는 Apify 크레딧이 건당 약 $0.0027 추가로 소모됩니다.

배경 이미지를 직접 준비할 계획이라면 noAI: true + imageUrl 조합이 가장 빠르고 저렴합니다.

디자인 설정

style 객체로 카드 모양을 조정합니다.

적용 우선순위: 요청의 style → 회원이 웹에서 계정에 저장한 디자인 → 서버 기본값
웹 화면(뉴스카드 → 디자인)에서 값을 맞춘 뒤 "이 디자인을 내 계정에 저장"을 누르면 API로 만들 때도 같은 디자인이 나옵니다. 저장하지 않으면 API는 서버 기본값을 씁니다.
{
  "url": "https://...",
  "style": {
    "hw": 900, "hmax": 76, "lh": 1.25, "tr": -0.03,
    "pad": 72, "bottom": 168, "badgeTop": 178,
    "grad": 0.52, "topgrad": 0.22,
    "shadow": 14, "align": "center",
    "sub": "자세한 내용은 본문 참고",
    "badge": "이해를 돕기 위한 AI 이미지"
  }
}
기본값설명
hw900헤드라인 굵기 (700 / 800 / 900)
hmax76헤드라인 최대 크기(px). 길면 자동 축소
lh1.25줄 간격 배수
tr-0.03자간 (em)
pad72좌우 여백(px)
bottom168하단 여백(px). 135 미만이면 인스타 1:1 화면에서 잘림
badgeTop178배지 높이(px). 135 미만이면 잘림
badgeSide40배지 가장자리 여백(px)
badgeSize20배지 글자 크기(px)
badgeAlignright배지 정렬 — left / center / right
badgeOpacity0.88배지 불투명도 (0~1)
subSize26서브 문구 크기(px)
subGap58서브 문구와 헤드라인 간격(px)
subOpacity0.72서브 문구 불투명도 (0~1)
grad0.52하단 그라데이션 높이 (0~1)
topgrad0.22상단 그라데이션 높이 (0~1)
shadow14글자 그림자 세기
aligncentercenter / left
sub자세한 내용은 본문 참고하단 서브 문구. ""면 표시 안 함
badge이해를 돕기 위한 AI 이미지우상단 배지. ""면 표시 안 함

폰트는 Paperlogy로 고정입니다.

프롬프트 커스터마이즈

둘 다 생략하면 기본 지침으로 동작합니다.

copyPrompt — 카피 지침 덮어쓰기

문체·톤·길이 규칙을 직접 지정합니다. 출력 형식(후보 10개, 캡션·해시태그 분리)은 스키마로 강제되므로 깨지지 않습니다.

{ "copyPrompt": "20대 여성 독자 대상, 친근한 반말체. 헤드라인은 질문형 위주로." }

imageStyle — 이미지 스타일 추가

생성된 이미지 프롬프트 뒤에 덧붙습니다. 매번 공통 적용할 스타일에 적합합니다.

{ "imageStyle": "always Korean people in their 20s-30s, warm color grading, subtle film grain" }
"글자 없음"과 "하단 여백 비우기"는 서버가 항상 자동으로 붙입니다. 배경에 깨진 글자가 생기는 것을 막기 위함이며 해제할 수 없습니다.

오류

{ "error": "인스타그램은 Apify 토큰이 필요합니다.", "needsApify": true }
코드의미대처
400잘못된 요청 / 키 없음본문·설정 확인
401API 키 오류X-API-Key 확인
404카드 없음 또는 만료6시간 경과했는지 확인
422본문 추출 실패text로 본문 직접 전달
429속도 제한잠시 후 재시도
502외부 API 오류재시도
여러 건을 한꺼번에 처리할 때는 동시 3건 이하를 권장합니다. 그 이상은 429가 발생할 수 있습니다.

텔레그램 봇 예제

링크를 받아 카드를 만들고, 헤드라인 후보를 버튼으로 보여주는 최소 구현입니다.

const API = 'https://algoajae.info';
const KEY = process.env.CARD_API_KEY;

// 1) 링크 수신 → 카드 생성
async function onLink(chatId, url) {
  await tg('sendMessage', { chat_id: chatId, text: '카드 만드는 중… (약 1분)' });

  const r = await fetch(`${API}/api/cards`, {
    method: 'POST',
    headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ url }),
  });
  const card = await r.json();
  if (!r.ok) return tg('sendMessage', { chat_id: chatId, text: '실패: ' + card.error });

  // 2) 이미지 + 캡션 전송 (imageUrl을 그대로 넘기면 됨)
  await tg('sendPhoto', {
    chat_id: chatId,
    photo: card.imageUrl,
    caption: `${card.caption}\n\n${card.hashtagText}\n\n비용 ${card.cost.krw}원`,
  });

  // 3) 헤드라인 후보를 버튼으로
  await tg('sendMessage', {
    chat_id: chatId,
    text: '다른 헤드라인으로 바꿀까요? (무료)',
    reply_markup: {
      inline_keyboard: card.variants.map((v, i) => [{
        text: `${v.line1} / ${v.line2}`,
        callback_data: `v:${card.cardId}:${i}`,
      }]),
    },
  });
}

// 4) 버튼 클릭 → 해당 후보로 교체 (추가 비용 없음)
async function onPick(chatId, cardId, index) {
  const r = await fetch(`${API}/api/cards/${cardId}/variant`, {
    method: 'POST',
    headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ index: Number(index) }),
  });
  const card = await r.json();
  await tg('sendPhoto', { chat_id: chatId, photo: card.imageUrl });
}

function tg(method, body) {
  return fetch(`https://api.telegram.org/bot${process.env.TG_TOKEN}/${method}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then(r => r.json());
}

60초 대기가 부담스럽다면

await fetch(`${API}/api/cards`, {
  method: 'POST',
  headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    url,
    async: true,
    webhookUrl: `https://my-bot.example.com/card-done?chat=${chatId}`,
  }),
});
// → 즉시 202. 완료되면 webhookUrl로 카드 정보가 POST됨

더 빠르고 저렴하게

{ "url": "https://...", "noAI": true, "imageUrl": "https://my-cdn/photo.jpg" }
// 약 6초, 13원