본문으로 건너뛰기
Previous
Next
무료 API 완벽 가이드 | 개인 프로젝트용 공공 API 100+ 총정리

무료 API 완벽 가이드 | 개인 프로젝트용 공공 API 100+ 총정리

무료 API 완벽 가이드 | 개인 프로젝트용 공공 API 100+ 총정리

이 글의 핵심

개인 프로젝트에 바로 사용할 수 있는 무료 공공 API 100개 이상. 날씨, 지도, 금융, AI, 이미지, 뉴스, 게임, 공공데이터까지. 인증 방법, 사용 예제, 제한사항 완벽 정리.

들어가며: “프로젝트에 실제 데이터를 넣고 싶어요”

토이 프로젝트의 고민

포트폴리오용 날씨 앱을 만들고 싶은데 실제 날씨 데이터가 없거나, 지도 서비스를 구현하고 싶은데 지도 API가 비싸거나, 주식 차트를 그리고 싶은데 금융 데이터를 구할 수 없어 고민한 적 있으신가요? 다행히도 수많은 기업과 정부 기관이 무료 공공 API를 제공하여 개인 개발자도 실전 같은 프로젝트를 만들 수 있습니다. 이 글에서 다루는 것:

  • 100개 이상의 무료 API 카테고리별 정리
  • 각 API의 특징, 제한사항, 사용 예제
  • API 키 발급 및 인증 방법
  • 실전 프로젝트 아이디어

실전 경험에서 배운 교훈

이 기술을 실무 프로젝트에 처음 도입했을 때, 공식 문서만으로는 알 수 없었던 많은 함정들이 있었습니다. 특히 프로덕션 환경에서 발생하는 엣지 케이스들은 로컬 개발 환경에서는 재현조차 되지 않았죠.

가장 어려웠던 점은 성능 최적화였습니다. 처음엔 “동작만 하면 되겠지”라고 생각했지만, 실제 사용자 트래픽이 몰리면서 병목 지점들이 하나씩 드러났습니다. 특히 데이터베이스 쿼리 최적화, 캐싱 전략, 에러 핸들링 구조 등은 여러 번의 장애를 겪으면서 개선해 나갔습니다.

이 글에서는 그런 시행착오를 통해 얻은 실전 노하우와, “이렇게 하면 안 된다”는 교훈들을 함께 정리했습니다. 특히 트러블슈팅 섹션은 실제 장애 대응 경험을 바탕으로 작성했으니, 비슷한 문제를 마주했을 때 참고하시면 도움이 될 것입니다.

1. 날씨 API

OpenWeatherMap

특징: 현재 날씨, 5일 예보, 16일 예보, 과거 데이터
제한: 60 calls/min, 1,000,000 calls/month (무료)
인증: API Key

// 현재 날씨
const API_KEY = 'your_api_key';
const city = 'Seoul';
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=kr`)
  .then(res => res.json())
  .then(data => {
    console.log(`도시: ${data.name}`);
    console.log(`온도: ${data.main.temp}°C`);
    console.log(`날씨: ${data.weather[0].description}`);
    console.log(`습도: ${data.main.humidity}%`);
  });
// 5일 예보
fetch(`https://api.openweathermap.org/data/2.5/forecast?q=${city}&appid=${API_KEY}&units=metric&lang=kr`)
  .then(res => res.json())
  .then(data => {
    data.list.forEach(item => {
      console.log(`${item.dt_txt}: ${item.main.temp}°C, ${item.weather[0].description}`);
    });
  });

회원가입: https://openweathermap.org/api

WeatherAPI

특징: 실시간 날씨, 14일 예보, 천문 데이터, 공기질
제한: 1,000,000 calls/month (무료)
인증: API Key

import requests
API_KEY = 'your_api_key'
city = 'Seoul'
# 현재 날씨
url = f'http://api.weatherapi.com/v1/current.json?key={API_KEY}&q={city}&lang=ko'
response = requests.get(url)
data = response.json()
print(f"위치: {data['location']['name']}")
print(f"온도: {data['current']['temp_c']}°C")
print(f"날씨: {data['current']['condition']['text']}")
# 7일 예보
url = f'http://api.weatherapi.com/v1/forecast.json?key={API_KEY}&q={city}&days=7&lang=ko'
response = requests.get(url)
data = response.json()
for day in data['forecast']['forecastday']:
    print(f"{day['date']}: 최고 {day['day']['maxtemp_c']}°C, 최저 {day['day']['mintemp_c']}°C")

회원가입: https://www.weatherapi.com/

기상청 API (한국)

특징: 초단기예보, 단기예보, 중기예보, 특보
제한: 일 1,000건 (무료)
인증: 공공데이터포털 API Key

import requests
from datetime import datetime
API_KEY = 'your_api_key'
base_url = 'http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst'
# 초단기실황
params = {
    'serviceKey': API_KEY,
    'numOfRows': 10,
    'pageNo': 1,
    'dataType': 'JSON',
    'base_date': datetime.now().strftime('%Y%m%d'),
    'base_time': '0600',
    'nx': 60,  # 격자 X (서울)
    'ny': 127  # 격자 Y
}
response = requests.get(base_url, params=params)
data = response.json()
for item in data['response']['body']['items']['item']:
    category = item['category']
    value = item['obsrValue']
    print(f"{category}: {value}")

회원가입: https://www.data.go.kr/

2. 지도 및 위치 API

Google Maps API

특징: 지도, 경로, 장소 검색, Geocoding, Street View
제한: $200/month 무료 크레딧 (약 28,000 지도 로드)
인증: API Key

// Geocoding (주소 → 좌표)
// 변수 선언 및 초기화
const address = '서울특별시 강남구 테헤란로';
const API_KEY = 'your_api_key';
fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    const location = data.results[0].geometry.location;
    console.log(`위도: ${location.lat}, 경도: ${location.lng}`);
  });
// 장소 검색
fetch(`https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=37.5665,126.9780&radius=1000&type=restaurant&key=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    data.results.forEach(place => {
      console.log(`${place.name}: ${place.vicinity}`);
    });
  });
<!-- 지도 임베드 -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
<div id="map" style="height: 400px;"></div>
<script>
function initMap() {
  const seoul = { lat: 37.5665, lng: 126.9780 };
  const map = new google.maps.Map(document.getElementById('map'), {
    zoom: 13,
    center: seoul
  });
  
  new google.maps.Marker({
    position: seoul,
    map: map,
    title: '서울'
  });
}
initMap();
</script>

회원가입: https://developers.google.com/maps

Kakao Map API (한국)

특징: 지도, 검색, 경로, 주소 변환 (한국 특화)
제한: 일 300,000건 (무료)
인증: REST API Key

// 주소 검색
const REST_API_KEY = 'your_api_key';
const address = '서울특별시 강남구 테헤란로 152';
fetch(`https://dapi.kakao.com/v2/local/search/address.json?query=${encodeURIComponent(address)}`, {
  headers: {
    'Authorization': `KakaoAK ${REST_API_KEY}`
  }
})
  .then(res => res.json())
  .then(data => {
    const result = data.documents[0];
    console.log(`주소: ${result.address_name}`);
    console.log(`좌표: ${result.x}, ${result.y}`);
  });
// 키워드 검색
fetch(`https://dapi.kakao.com/v2/local/search/keyword.json?query=카페&x=126.9780&y=37.5665&radius=1000`, {
  headers: {
    'Authorization': `KakaoAK ${REST_API_KEY}`
  }
})
  .then(res => res.json())
  .then(data => {
    data.documents.forEach(place => {
      console.log(`${place.place_name}: ${place.address_name}`);
    });
  });

회원가입: https://developers.kakao.com/

OpenStreetMap (Nominatim)

특징: 무료 오픈소스 지도, Geocoding
제한: 1 request/sec (무료)
인증: 불필요 (User-Agent 필수)

import requests
import time
def geocode(address):
    """주소 → 좌표"""
    url = 'https://nominatim.openstreetmap.org/search'
    params = {
        'q': address,
        'format': 'json',
        'limit': 1
    }
    headers = {
        'User-Agent': 'MyApp/1.0 ([email protected])'  # 필수!
    }
    
    response = requests.get(url, params=params, headers=headers)
    data = response.json()
    
    if data:
        return {
            'lat': float(data[0]['lat']),
            'lon': float(data[0]['lon']),
            'display_name': data[0]['display_name']
        }
    return None
result = geocode('Seoul, South Korea')
print(result)
time.sleep(1)  # Rate limit 준수

문서: https://nominatim.org/release-docs/latest/api/Overview/

3. 금융 및 암호화폐 API

Alpha Vantage (주식, 환율)

특징: 실시간 주식, 환율, 암호화폐, 기술 지표
제한: 25 requests/day (무료)
인증: API Key

const API_KEY = 'your_api_key';
// 주식 시세 (실시간)
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AAPL&apikey=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    const quote = data['Global Quote'];
    console.log(`Symbol: ${quote['01. symbol']}`);
    console.log(`Price: $${quote['05. price']}`);
    console.log(`Change: ${quote['09. change']} (${quote['10. change percent']})`);
  });
// 일일 시계열 (최근 100일)
fetch(`https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=AAPL&apikey=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    const timeSeries = data['Time Series (Daily)'];
    Object.entries(timeSeries).slice(0, 5).forEach(([date, values]) => {
      console.log(`${date}: Open $${values['1. open']}, Close $${values['4. close']}`);
    });
  });
// 환율
fetch(`https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=USD&to_currency=KRW&apikey=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    const rate = data['Realtime Currency Exchange Rate'];
    console.log(`1 USD = ${rate['5. Exchange Rate']} KRW`);
  });

회원가입: https://www.alphavantage.co/support/#api-key

CoinGecko (암호화폐)

특징: 암호화폐 시세, 시가총액, 거래량, 차트
제한: 10-50 calls/min (무료)
인증: 불필요 (API Key 선택적)

import requests
# 암호화폐 시세
url = 'https://api.coingecko.com/api/v3/simple/price'
params = {
    'ids': 'bitcoin,ethereum,ripple',
    'vs_currencies': 'usd,krw',
    'include_24hr_change': 'true'
}
response = requests.get(url, params=params)
data = response.json()
for coin, prices in data.items():
    print(f"{coin.upper()}:")
    print(f"  USD: ${prices['usd']:,.2f} ({prices['usd_24h_change']:+.2f}%)")
    print(f"  KRW: ₩{prices['krw']:,.0f} ({prices['krw_24h_change']:+.2f}%)")
# 시가총액 상위 10개
url = 'https://api.coingecko.com/api/v3/coins/markets'
params = {
    'vs_currency': 'usd',
    'order': 'market_cap_desc',
    'per_page': 10,
    'page': 1
}
response = requests.get(url, params=params)
coins = response.json()
for i, coin in enumerate(coins, 1):
    print(f"{i}. {coin['name']} ({coin['symbol'].upper()}): ${coin['current_price']:,.2f}")

문서: https://www.coingecko.com/en/api

ExchangeRate-API (환율)

특징: 실시간 환율, 161개 통화
제한: 1,500 requests/month (무료)
인증: API Key

const API_KEY = 'your_api_key';
// 환율 조회
fetch(`https://v6.exchangerate-api.com/v6/${API_KEY}/latest/USD`)
  .then(res => res.json())
  .then(data => {
    console.log(`1 USD = ${data.conversion_rates.KRW} KRW`);
    console.log(`1 USD = ${data.conversion_rates.JPY} JPY`);
    console.log(`1 USD = ${data.conversion_rates.EUR} EUR`);
  });
// 환전 계산
const amount = 100;
fetch(`https://v6.exchangerate-api.com/v6/${API_KEY}/pair/USD/KRW/${amount}`)
  .then(res => res.json())
  .then(data => {
    console.log(`$${amount} = ₩${data.conversion_result}`);
  });

회원가입: https://www.exchangerate-api.com/

4. AI 및 머신러닝 API

OpenAI API

특징: GPT-4, GPT-3.5, DALL-E, Whisper
제한: $5 무료 크레딧 (신규 가입)
인증: API Key

import openai
openai.api_key = 'your_api_key'
# ChatGPT
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Python으로 피보나치 수열 구현해줘"}
    ]
)
print(response.choices[0].message.content)
# 이미지 생성 (DALL-E)
response = openai.Image.create(
    prompt="A futuristic city with flying cars",
    n=1,
    size="1024x1024"
)
image_url = response.data[0].url
print(f"Image URL: {image_url}")

회원가입: https://platform.openai.com/

Hugging Face API

특징: 수천 개의 AI 모델 (텍스트, 이미지, 음성)
제한: 30,000 requests/month (무료)
인증: API Token

import requests
API_TOKEN = 'your_api_token'
headers = {"Authorization": f"Bearer {API_TOKEN}"}
# 감정 분석
API_URL = "https://api-inference.huggingface.co/models/distilbert-base-uncased-finetuned-sst-2-english"
data = {"inputs": "I love this product!"}
response = requests.post(API_URL, headers=headers, json=data)
result = response.json()
print(result)
# [{'label': 'POSITIVE', 'score': 0.9998}]
# 텍스트 생성
API_URL = "https://api-inference.huggingface.co/models/gpt2"
data = {"inputs": "Once upon a time"}
response = requests.post(API_URL, headers=headers, json=data)
print(response.json()[0]['generated_text'])
# 이미지 분류
API_URL = "https://api-inference.huggingface.co/models/google/vit-base-patch16-224"
with open('image.jpg', 'rb') as f:
    data = f.read()
response = requests.post(API_URL, headers=headers, data=data)
print(response.json())

회원가입: https://huggingface.co/

Google Gemini API

특징: Gemini Pro, 멀티모달 (텍스트, 이미지)
제한: 60 requests/min (무료)
인증: API Key

import google.generativeai as genai
genai.configure(api_key='your_api_key')
model = genai.GenerativeModel('gemini-pro')
# 텍스트 생성
response = model.generate_content('Python으로 웹 크롤러 만드는 방법 설명해줘')
print(response.text)
# 멀티모달 (이미지 + 텍스트)
model = genai.GenerativeModel('gemini-pro-vision')
image = PIL.Image.open('photo.jpg')
response = model.generate_content(['이 이미지에 뭐가 있나요?', image])
print(response.text)

회원가입: https://makersuite.google.com/app/apikey

5. 이미지 및 미디어 API

Unsplash API

특징: 고품질 무료 이미지 (200만+ 장)
제한: 50 requests/hour (무료)
인증: Access Key

const ACCESS_KEY = 'your_access_key';
// 랜덤 이미지
fetch(`https://api.unsplash.com/photos/random?client_id=${ACCESS_KEY}`)
  .then(res => res.json())
  .then(data => {
    console.log(`Image URL: ${data.urls.regular}`);
    console.log(`Author: ${data.user.name}`);
  });
// 검색
fetch(`https://api.unsplash.com/search/photos?query=nature&client_id=${ACCESS_KEY}`)
  .then(res => res.json())
  .then(data => {
    data.results.forEach(photo => {
      console.log(`${photo.alt_description}: ${photo.urls.small}`);
    });
  });

회원가입: https://unsplash.com/developers

Pexels API

특징: 무료 이미지 및 비디오
제한: 200 requests/hour (무료)
인증: API Key

import requests
API_KEY = 'your_api_key'
headers = {'Authorization': API_KEY}
# 이미지 검색
url = 'https://api.pexels.com/v1/search'
params = {'query': 'nature', 'per_page': 10}
response = requests.get(url, headers=headers, params=params)
data = response.json()
for photo in data['photos']:
    print(f"Photo: {photo['src']['medium']}")
    print(f"Photographer: {photo['photographer']}")
# 비디오 검색
url = 'https://api.pexels.com/videos/search'
params = {'query': 'ocean', 'per_page': 5}
response = requests.get(url, headers=headers, params=params)
data = response.json()
for video in data['videos']:
    print(f"Video: {video['video_files'][0]['link']}")

회원가입: https://www.pexels.com/api/

Lorem Picsum (플레이스홀더 이미지)

특징: 랜덤 이미지 (테스트용)
제한: 무제한
인증: 불필요

<!-- 특정 크기 -->
<img src="https://picsum.photos/800/600" alt="Random">
<!-- 특정 이미지 ID -->
<img src="https://picsum.photos/id/237/800/600" alt="Dog">
<!-- 그레이스케일 -->
<img src="https://picsum.photos/800/600?grayscale" alt="Grayscale">
<!-- 블러 -->
<img src="https://picsum.photos/800/600?blur=5" alt="Blurred">

문서: https://picsum.photos/

6. 뉴스 및 소셜 API

NewsAPI

특징: 전 세계 뉴스, 80,000+ 소스
제한: 100 requests/day (무료)
인증: API Key

const API_KEY = 'your_api_key';
// 헤드라인
fetch(`https://newsapi.org/v2/top-headlines?country=kr&apiKey=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    data.articles.forEach(article => {
      console.log(`${article.title}`);
      console.log(`Source: ${article.source.name}`);
      console.log(`URL: ${article.url}\n`);
    });
  });
// 키워드 검색
fetch(`https://newsapi.org/v2/everything?q=artificial intelligence&sortBy=publishedAt&apiKey=${API_KEY}`)
  .then(res => res.json())
  .then(data => {
    console.log(`Total results: ${data.totalResults}`);
    data.articles.slice(0, 5).forEach(article => {
      console.log(`${article.title} (${article.publishedAt})`);
    });
  });

회원가입: https://newsapi.org/

Reddit API

특징: Reddit 게시글, 댓글, 서브레딧
제한: 60 requests/min (무료)
인증: OAuth2 (또는 불필요)

import requests
# 인증 없이 공개 데이터 조회
url = 'https://www.reddit.com/r/programming/hot.json'
headers = {'User-Agent': 'MyApp/1.0'}
response = requests.get(url, headers=headers)
data = response.json()
for post in data['data']['children'][:10]:
    p = post['data']
    print(f"Title: {p['title']}")
    print(f"Score: {p['score']} | Comments: {p['num_comments']}")
    print(f"URL: https://reddit.com{p['permalink']}\n")
# 검색
url = 'https://www.reddit.com/search.json'
params = {'q': 'python tutorial', 'limit': 10}
response = requests.get(url, headers=headers, params=params)
data = response.json()

문서: https://www.reddit.com/dev/api/

7. 게임 및 엔터테인먼트 API

PokeAPI (포켓몬)

특징: 포켓몬 데이터베이스 (1,000+ 포켓몬)
제한: 무제한 (캐싱 권장)
인증: 불필요

// 포켓몬 정보
fetch('https://pokeapi.co/api/v2/pokemon/pikachu')
  .then(res => res.json())
  .then(data => {
    console.log(`Name: ${data.name}`);
    console.log(`Height: ${data.height}`);
    console.log(`Weight: ${data.weight}`);
    console.log(`Types: ${data.types.map(t => t.type.name).join(', ')}`);
    console.log(`Abilities: ${data.abilities.map(a => a.ability.name).join(', ')}`);
    console.log(`Sprite: ${data.sprites.front_default}`);
  });
// 포켓몬 목록
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
  .then(res => res.json())
  .then(data => {
    data.results.forEach((pokemon, i) => {
      console.log(`${i+1}. ${pokemon.name}`);
    });
  });

문서: https://pokeapi.co/

RAWG (비디오 게임 데이터베이스)

특징: 800,000+ 게임 정보, 리뷰, 스크린샷
제한: 20,000 requests/month (무료)
인증: API Key

import requests
API_KEY = 'your_api_key'
# 게임 검색
url = 'https://api.rawg.io/api/games'
params = {
    'key': API_KEY,
    'search': 'witcher',
    'page_size': 5
}
response = requests.get(url, params=params)
data = response.json()
for game in data['results']:
    print(f"Title: {game['name']}")
    print(f"Rating: {game['rating']}/5")
    print(f"Released: {game['released']}")
    print(f"Platforms: {', '.join([p['platform']['name'] for p in game['platforms']])}\n")
# 게임 상세 정보
game_id = 3328  # The Witcher 3
url = f'https://api.rawg.io/api/games/{game_id}'
params = {'key': API_KEY}
response = requests.get(url, params=params)
game = response.json()
print(f"Description: {game['description_raw'][:200]}...")

회원가입: https://rawg.io/apidocs

The Movie Database (TMDB)

특징: 영화, TV 프로그램, 배우 정보
제한: 무제한 (Rate limit 있음)
인증: API Key

const API_KEY = 'your_api_key';
// 인기 영화
fetch(`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}&language=ko-KR`)
  .then(res => res.json())
  .then(data => {
    data.results.forEach(movie => {
      console.log(`${movie.title} (${movie.release_date})`);
      console.log(`Rating: ${movie.vote_average}/10`);
      console.log(`Poster: https://image.tmdb.org/t/p/w500${movie.poster_path}\n`);
    });
  });
// 영화 검색
fetch(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=Inception&language=ko-KR`)
  .then(res => res.json())
  .then(data => {
    const movie = data.results[0];
    console.log(`Title: ${movie.title}`);
    console.log(`Overview: ${movie.overview}`);
  });

회원가입: https://www.themoviedb.org/settings/api

8. 한국 공공데이터 API

공공데이터포털

제공 API:

  • 국토교통부: 아파트 실거래가, 건축물 대장
  • 환경부: 대기오염 정보, 미세먼지
  • 행정안전부: 주소 검색, 우편번호
  • 문화체육관광부: 관광 정보, 축제
  • 보건복지부: 병원 정보, 약국 위치
import requests
API_KEY = 'your_api_key'
# 아파트 실거래가
url = 'http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev'
params = {
    'serviceKey': API_KEY,
    'LAWD_CD': '11110',  # 지역코드 (서울 종로구)
    'DEAL_YMD': '202604',  # 거래년월
    'numOfRows': 10
}
response = requests.get(url, params=params)
# XML 파싱 필요
# 미세먼지 정보
url = 'http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty'
params = {
    'serviceKey': API_KEY,
    'returnType': 'json',
    'numOfRows': 1,
    'stationName': '종로구',
    'dataTerm': 'DAILY'
}
response = requests.get(url, params=params)
data = response.json()
item = data['response']['body']['items'][0]
print(f"측정소: {item['stationName']}")
print(f"미세먼지(PM10): {item['pm10Value']}㎍/㎥")
print(f"초미세먼지(PM2.5): {item['pm25Value']}㎍/㎥")
print(f"통합대기환경지수: {item['khaiValue']} ({item['khaiGrade']})")

회원가입: https://www.data.go.kr/

서울 열린데이터광장

제공 API:

  • 실시간 지하철 도착 정보
  • 공공자전거 대여소 정보
  • 문화행사 정보
  • CCTV 교통 정보
import requests
API_KEY = 'your_api_key'
# 지하철 실시간 도착 정보
url = f'http://swopenapi.seoul.go.kr/api/subway/{API_KEY}/json/realtimeStationArrival/0/5/서울'
response = requests.get(url)
data = response.json()
for train in data['realtimeArrivalList']:
    print(f"[{train['subwayId']}호선] {train['trainLineNm']}")
    print(f"도착: {train['arvlMsg2']}")
    print(f"방향: {train['bstatnNm']}{train['statnNm']}{train['endStnNm']}\n")
# 따릉이 대여소 정보
url = f'http://openapi.seoul.go.kr:8088/{API_KEY}/json/bikeList/1/10/'
response = requests.get(url)
data = response.json()
for station in data['rentBikeStatus']['row']:
    print(f"{station['stationName']}: {station['parkingBikeTotCnt']}대 대여 가능")

회원가입: https://data.seoul.go.kr/

9. 기타 유용한 API

REST Countries

특징: 국가 정보 (250개국)
제한: 무제한
인증: 불필요

// 모든 국가
fetch('https://restcountries.com/v3.1/all')
  .then(res => res.json())
  .then(countries => {
    countries.forEach(country => {
      console.log(`${country.name.common}: ${country.capital?.[0]}`);
    });
  });
// 특정 국가
fetch('https://restcountries.com/v3.1/name/korea')
  .then(res => res.json())
  .then(data => {
    const country = data[0];
    console.log(`Name: ${country.name.common}`);
    console.log(`Capital: ${country.capital[0]}`);
    console.log(`Population: ${country.population.toLocaleString()}`);
    console.log(`Languages: ${Object.values(country.languages).join(', ')}`);
    console.log(`Flag: ${country.flag}`);
  });

문서: https://restcountries.com/

JSONPlaceholder (테스트용 가짜 API)

특징: REST API 테스트용 더미 데이터
제한: 무제한
인증: 불필요

// GET
fetch('https://jsonplaceholder.typicode.com/posts')
  .then(res => res.json())
  .then(posts => {
    posts.slice(0, 5).forEach(post => {
      console.log(`${post.id}. ${post.title}`);
    });
  });
// POST
fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    title: 'New Post',
    body: 'This is a new post',
    userId: 1
  })
})
  .then(res => res.json())
  .then(data => console.log('Created:', data));
// PUT
fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    id: 1,
    title: 'Updated Post',
    body: 'Updated content',
    userId: 1
  })
})
  .then(res => res.json())
  .then(data => console.log('Updated:', data));
// DELETE
fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'DELETE'
})
  .then(() => console.log('Deleted'));

문서: https://jsonplaceholder.typicode.com/

IP Geolocation

특징: IP 주소 위치 정보
제한: 1,000 requests/day (무료)
인증: 불필요

import requests
# 현재 IP 위치
response = requests.get('https://ipapi.co/json/')
data = response.json()
print(f"IP: {data['ip']}")
print(f"City: {data['city']}")
print(f"Region: {data['region']}")
print(f"Country: {data['country_name']}")
print(f"Latitude: {data['latitude']}")
print(f"Longitude: {data['longitude']}")
# 특정 IP 조회
ip = '8.8.8.8'
response = requests.get(f'https://ipapi.co/{ip}/json/')
data = response.json()
print(data)

문서: https://ipapi.co/

QR Code Generator

특징: QR 코드 생성
제한: 무제한
인증: 불필요

<!-- URL로 QR 코드 생성 -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://example.com" alt="QR Code">
<!-- 텍스트 -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=Hello%20World" alt="QR">
<!-- 색상 지정 -->
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://example.com&color=0000ff&bgcolor=ffffff" alt="Blue QR">

문서: https://goqr.me/api/

카테고리별 API 목록

날씨 및 환경

API특징제한인증
OpenWeatherMap날씨, 예보60/minAPI Key
WeatherAPI날씨, 공기질1M/monthAPI Key
기상청한국 날씨1,000/dayAPI Key
AirVisual공기질10,000/monthAPI Key

지도 및 위치

API특징제한인증
Google Maps지도, 경로$200 creditAPI Key
Kakao Map한국 지도300,000/dayAPI Key
Naver Map한국 지도제한 있음Client ID
OpenStreetMap오픈소스 지도1/sec불필요

금융 및 경제

API특징제한인증
Alpha Vantage주식, 환율25/dayAPI Key
CoinGecko암호화폐10-50/min불필요
ExchangeRate-API환율1,500/monthAPI Key
한국은행경제 통계제한 있음API Key

AI 및 머신러닝

API특징제한인증
OpenAIGPT, DALL-E$5 creditAPI Key
Hugging FaceAI 모델30,000/monthToken
Google GeminiGemini Pro60/minAPI Key
Stability AIStable Diffusion제한 있음API Key

이미지 및 미디어

API특징제한인증
Unsplash고품질 이미지50/hourAccess Key
Pexels이미지, 비디오200/hourAPI Key
Pixabay무료 이미지100/minAPI Key
Lorem Picsum플레이스홀더무제한불필요

뉴스 및 소셜

API특징제한인증
NewsAPI전 세계 뉴스100/dayAPI Key
RedditReddit 데이터60/minOAuth2
Twitter트윗, 타임라인제한 있음OAuth
YouTube비디오 검색10,000 units/dayAPI Key

게임 및 엔터테인먼트

API특징제한인증
PokeAPI포켓몬무제한불필요
RAWG게임 DB20,000/monthAPI Key
TMDB영화, TV무제한API Key
Spotify음악 메타데이터제한 있음OAuth

개발 도구

API특징제한인증
GitHub저장소, 이슈5,000/hourToken
JSONPlaceholder테스트 API무제한불필요
Random User가짜 사용자무제한불필요
Lorem Ipsum더미 텍스트무제한불필요

10. API 사용 베스트 프랙티스

1. API 키 보안

# .env 파일
OPENWEATHER_API_KEY=abc123def456
GOOGLE_MAPS_API_KEY=xyz789uvw012
NEWS_API_KEY=qwe456rty789
# .gitignore에 추가
.env
.env.local
.env.production
// Node.js (dotenv)
require('dotenv').config();
const API_KEY = process.env.OPENWEATHER_API_KEY;
fetch(`https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=${API_KEY}`)
  .then(res => res.json())
  .then(data => console.log(data));
# Python (python-dotenv)
from dotenv import load_dotenv
import os
load_dotenv()
API_KEY = os.getenv('OPENWEATHER_API_KEY')

2. Rate Limiting 처리

import time
import requests
from functools import wraps
def rate_limit(max_per_second):
    """Rate limiting 데코레이터"""
    min_interval = 1.0 / max_per_second
    last_called = [0.0]
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            elapsed = time.time() - last_called[0]
            left_to_wait = min_interval - elapsed
            
            if left_to_wait > 0:
                time.sleep(left_to_wait)
            
            result = func(*args, **kwargs)
            last_called[0] = time.time()
            return result
        
        return wrapper
    return decorator
@rate_limit(1)  # 초당 1회
def fetch_weather(city):
    url = f'https://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}'
    return requests.get(url).json()
# 사용
cities = ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Daejeon']
for city in cities:
    data = fetch_weather(city)
    print(f"{city}: {data['main']['temp']}°C")
    # 자동으로 1초 대기

3. 에러 처리

async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      
      if (!response.ok) {
        if (response.status === 429) {
          // Rate limit 초과
          const retryAfter = response.headers.get('Retry-After') || 60;
          console.log(`⏳ Rate limited. Waiting ${retryAfter}s...`);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }
        
        if (response.status >= 500) {
          // 서버 오류 (재시도)
          console.log(`❌ Server error (${response.status}). Retry ${i+1}/${maxRetries}...`);
          await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
          continue;
        }
        
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      return await response.json();
      
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      console.log(`❌ Error: ${error.message}. Retry ${i+1}/${maxRetries}...`);
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}
// 사용
try {
  const data = await fetchWithRetry('https://api.example.com/data');
  console.log(data);
} catch (error) {
  console.error('Failed after retries:', error);
}

4. 캐싱

import requests
import time
from functools import lru_cache
import hashlib
import json
import os
class APICache:
    def __init__(self, cache_dir='.cache', ttl=3600):
        self.cache_dir = cache_dir
        self.ttl = ttl
        os.makedirs(cache_dir, exist_ok=True)
    
    def get_cache_path(self, url):
        """URL을 캐시 파일 경로로 변환"""
        url_hash = hashlib.md5(url.encode()).hexdigest()
        return os.path.join(self.cache_dir, f"{url_hash}.json")
    
    def get(self, url):
        """캐시에서 데이터 조회"""
        cache_path = self.get_cache_path(url)
        
        if os.path.exists(cache_path):
            mtime = os.path.getmtime(cache_path)
            if time.time() - mtime < self.ttl:
                with open(cache_path, 'r') as f:
                    return json.load(f)
        
        return None
    
    def set(self, url, data):
        """캐시에 데이터 저장"""
        cache_path = self.get_cache_path(url)
        with open(cache_path, 'w') as f:
            json.dump(data, f)
def fetch_with_cache(url, cache_ttl=3600):
    """캐싱된 API 요청"""
    cache = APICache(ttl=cache_ttl)
    
    # 캐시 확인
    cached = cache.get(url)
    if cached:
        print(f"✅ Cache hit: {url}")
        return cached
    
    # API 요청
    print(f"📡 Fetching: {url}")
    response = requests.get(url)
    data = response.json()
    
    # 캐시 저장
    cache.set(url, data)
    
    return data
# 사용
url = f'https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid={API_KEY}'
data = fetch_with_cache(url, cache_ttl=1800)  # 30분 캐싱
print(data)

5. 백엔드 프록시 (API 키 숨기기)

// server.js (Express)
const express = require('express');
const axios = require('axios');
require('dotenv').config();
const app = express();
// CORS 설정
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  next();
});
// 날씨 API 프록시
app.get('/api/weather/:city', async (req, res) => {
  try {
    const { city } = req.params;
    const API_KEY = process.env.OPENWEATHER_API_KEY;
    
    const response = await axios.get(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`
    );
    
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
// 뉴스 API 프록시
app.get('/api/news', async (req, res) => {
  try {
    const API_KEY = process.env.NEWS_API_KEY;
    const response = await axios.get(
      `https://newsapi.org/v2/top-headlines?country=kr&apiKey=${API_KEY}`
    );
    
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
app.listen(3000, () => {
  console.log('✅ Proxy server running on port 3000');
});
// 프론트엔드에서 프록시 사용
fetch('/api/weather/Seoul')
  .then(res => res.json())
  .then(data => console.log(data));
// API 키가 노출되지 않음!

실전 프로젝트 아이디어

프로젝트 1: 날씨 대시보드

// weather-dashboard.js
const API_KEY = process.env.OPENWEATHER_API_KEY;
async function createWeatherDashboard() {
  const cities = ['Seoul', 'Busan', 'Incheon', 'Daegu', 'Daejeon'];
  
  const weatherData = await Promise.all(
    cities.map(city =>
      fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=kr`)
        .then(res => res.json())
    )
  );
  
  weatherData.forEach(data => {
    console.log(`\n${data.name}`);
    console.log(`  온도: ${data.main.temp}°C (체감: ${data.main.feels_like}°C)`);
    console.log(`  날씨: ${data.weather[0].description}`);
    console.log(`  습도: ${data.main.humidity}%`);
    console.log(`  풍속: ${data.wind.speed}m/s`);
  });
}
createWeatherDashboard();

프로젝트 2: 암호화폐 트래커

import requests
import time
from datetime import datetime
def crypto_tracker():
    """실시간 암호화폐 가격 추적"""
    
    coins = ['bitcoin', 'ethereum', 'ripple', 'cardano', 'solana']
    
    while True:
        url = 'https://api.coingecko.com/api/v3/simple/price'
        params = {
            'ids': ','.join(coins),
            'vs_currencies': 'usd,krw',
            'include_24hr_change': 'true',
            'include_market_cap': 'true'
        }
        
        response = requests.get(url, params=params)
        data = response.json()
        
        print(f"\n{'='*60}")
        print(f"Crypto Tracker - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"{'='*60}\n")
        
        for coin, prices in data.items():
            change = prices['usd_24h_change']
            emoji = '📈' if change > 0 else '📉'
            
            print(f"{emoji} {coin.upper()}")
            print(f"   USD: ${prices['usd']:,.2f} ({change:+.2f}%)")
            print(f"   KRW: ₩{prices['krw']:,.0f}")
            print(f"   Cap: ${prices['usd_market_cap']:,.0f}\n")
        
        time.sleep(60)  # 1분마다 업데이트
crypto_tracker()

프로젝트 3: 뉴스 애그리게이터

// news-aggregator.js
const NEWS_API_KEY = process.env.NEWS_API_KEY;
async function fetchNews(category = 'technology') {
  const url = `https://newsapi.org/v2/top-headlines?country=kr&category=${category}&apiKey=${NEWS_API_KEY}`;
  
  const response = await fetch(url);
  const data = await response.json();
  
  return data.articles.map(article => ({
    title: article.title,
    description: article.description,
    url: article.url,
    source: article.source.name,
    publishedAt: new Date(article.publishedAt),
    image: article.urlToImage
  }));
}
async function createNewsFeed() {
  const categories = ['technology', 'business', 'science'];
  
  for (const category of categories) {
    console.log(`\n📰 ${category.toUpperCase()}\n`);
    
    const articles = await fetchNews(category);
    
    articles.slice(0, 5).forEach((article, i) => {
      console.log(`${i+1}. ${article.title}`);
      console.log(`   Source: ${article.source}`);
      console.log(`   URL: ${article.url}\n`);
    });
  }
}
createNewsFeed();

프로젝트 4: 영화 추천 시스템

import requests
TMDB_API_KEY = 'your_api_key'
def get_popular_movies(page=1):
    """인기 영화 조회"""
    url = f'https://api.themoviedb.org/3/movie/popular'
    params = {
        'api_key': TMDB_API_KEY,
        'language': 'ko-KR',
        'page': page
    }
    
    response = requests.get(url, params=params)
    return response.json()['results']
def get_movie_recommendations(movie_id):
    """영화 추천"""
    url = f'https://api.themoviedb.org/3/movie/{movie_id}/recommendations'
    params = {
        'api_key': TMDB_API_KEY,
        'language': 'ko-KR'
    }
    
    response = requests.get(url, params=params)
    return response.json()['results']
def search_movies(query):
    """영화 검색"""
    url = f'https://api.themoviedb.org/3/search/movie'
    params = {
        'api_key': TMDB_API_KEY,
        'language': 'ko-KR',
        'query': query
    }
    
    response = requests.get(url, params=params)
    return response.json()['results']
# 사용
print("🎬 인기 영화 Top 10")
movies = get_popular_movies()
for i, movie in enumerate(movies[:10], 1):
    print(f"{i}. {movie['title']} ({movie['release_date'][:4]})")
    print(f"   평점: {movie['vote_average']}/10")
print("\n🔍 '인셉션' 검색")
results = search_movies('Inception')
if results:
    movie = results[0]
    print(f"제목: {movie['title']}")
    print(f"개봉: {movie['release_date']}")
    print(f"줄거리: {movie['overview']}")
    
    print("\n💡 추천 영화")
    recommendations = get_movie_recommendations(movie['id'])
    for rec in recommendations[:5]:
        print(f"  - {rec['title']} ({rec['vote_average']}/10)")

API 통합 예제

멀티 API 대시보드

#!/usr/bin/env python3
"""
멀티 API 통합 대시보드
"""
import requests
import os
from datetime import datetime
class DashboardAPI:
    def __init__(self):
        self.weather_key = os.getenv('OPENWEATHER_API_KEY')
        self.news_key = os.getenv('NEWS_API_KEY')
        self.crypto_enabled = True
    
    def get_weather(self, city='Seoul'):
        """날씨 정보"""
        url = f'https://api.openweathermap.org/data/2.5/weather'
        params = {
            'q': city,
            'appid': self.weather_key,
            'units': 'metric',
            'lang': 'kr'
        }
        
        response = requests.get(url, params=params)
        data = response.json()
        
        return {
            'city': data['name'],
            'temp': data['main']['temp'],
            'description': data['weather'][0]['description'],
            'humidity': data['main']['humidity']
        }
    
    def get_crypto_prices(self):
        """암호화폐 시세"""
        url = 'https://api.coingecko.com/api/v3/simple/price'
        params = {
            'ids': 'bitcoin,ethereum',
            'vs_currencies': 'usd,krw',
            'include_24hr_change': 'true'
        }
        
        response = requests.get(url, params=params)
        return response.json()
    
    def get_news(self, category='technology'):
        """뉴스 헤드라인"""
        url = 'https://newsapi.org/v2/top-headlines'
        params = {
            'country': 'kr',
            'category': category,
            'apiKey': self.news_key,
            'pageSize': 5
        }
        
        response = requests.get(url, params=params)
        data = response.json()
        
        return [
            {
                'title': article['title'],
                'source': article['source']['name'],
                'url': article['url']
            }
            for article in data['articles']
        ]
    
    def display_dashboard(self):
        """대시보드 출력"""
        print(f"\n{'='*70}")
        print(f"📊 Dashboard - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"{'='*70}\n")
        
        # 날씨
        print("🌤️  날씨")
        weather = self.get_weather()
        print(f"   {weather['city']}: {weather['temp']}°C, {weather['description']}")
        print(f"   습도: {weather['humidity']}%\n")
        
        # 암호화폐
        print("💰 암호화폐")
        crypto = self.get_crypto_prices()
        for coin, prices in crypto.items():
            change = prices['usd_24h_change']
            emoji = '📈' if change > 0 else '📉'
            print(f"   {emoji} {coin.upper()}: ${prices['usd']:,.2f} ({change:+.2f}%)")
        print()
        
        # 뉴스
        print("📰 기술 뉴스")
        news = self.get_news('technology')
        for i, article in enumerate(news, 1):
            print(f"   {i}. {article['title']}")
            print(f"      {article['source']}\n")
# 실행
dashboard = DashboardAPI()
dashboard.display_dashboard()

API 키 관리 도구

환경 변수 관리

# .env.example (템플릿)
OPENWEATHER_API_KEY=your_key_here
GOOGLE_MAPS_API_KEY=your_key_here
NEWS_API_KEY=your_key_here
TMDB_API_KEY=your_key_here
# .env (실제 키, .gitignore에 추가)
OPENWEATHER_API_KEY=abc123def456
GOOGLE_MAPS_API_KEY=xyz789uvw012
NEWS_API_KEY=qwe456rty789
TMDB_API_KEY=asd789fgh012

Vercel/Netlify 환경 변수

# Vercel
vercel env add OPENWEATHER_API_KEY
# 값 입력: abc123def456
# Netlify
netlify env:set OPENWEATHER_API_KEY abc123def456
# GitHub Actions
# Settings → Secrets → New repository secret
# Name: OPENWEATHER_API_KEY
# Value: abc123def456

추가 무료 API 목록

유틸리티

📧 이메일 검증: https://hunter.io/ (50/month)
📱 SMS 전송: https://www.twilio.com/ ($15 credit)
🔗 URL 단축: https://tinyurl.com/app/dev (무제한)
📊 QR 코드: https://goqr.me/api/ (무제한)
🌐 번역: https://libretranslate.com/ (무제한)

데이터베이스

🗄️ Firebase: https://firebase.google.com/ (Spark 플랜)
🐘 Supabase: https://supabase.com/ (500MB)
🍃 MongoDB Atlas: https://www.mongodb.com/cloud/atlas (512MB)
🔥 PlanetScale: https://planetscale.com/ (5GB)

인증

// 실행 예제
🔐 Auth0: https://auth0.com/ (7,000 users)
🎫 Clerk: https://clerk.com/ (10,000 users)
👤 Firebase Auth: https://firebase.google.com/ (무제한)
🔑 Supabase Auth: https://supabase.com/ (50,000 users)

결제

💳 Stripe: https://stripe.com/ (테스트 모드 무제한)
💰 PayPal: https://developer.paypal.com/ (샌드박스)
🏦 Toss Payments: https://docs.tosspayments.com/ (테스트)

이메일

📬 SendGrid: https://sendgrid.com/ (100/day)
📮 Mailgun: https://www.mailgun.com/ (5,000/month)
✉️ Resend: https://resend.com/ (100/day)

분석

📈 Google Analytics: https://analytics.google.com/ (무제한)
📊 Plausible: https://plausible.io/ (10,000 pageviews)
🔍 PostHog: https://posthog.com/ (1M events)

API 테스트 도구

Postman

// Postman Collection 예시
{
  "info": {
    "name": "Weather API Collection",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Get Current Weather",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid={{API_KEY}}&units=metric",
          "host": ["api", "openweathermap", "org"],
          "path": ["data", "2.5", "weather"],
          "query": [
            {"key": "q", "value": "Seoul"},
            {"key": "appid", "value": "{{API_KEY}}"},
            {"key": "units", "value": "metric"}
          ]
        }
      }
    }
  ]
}

curl 테스트

# GET 요청
curl "https://api.openweathermap.org/data/2.5/weather?q=Seoul&appid=YOUR_API_KEY"
# 헤더 포함
curl -H "Authorization: Bearer YOUR_TOKEN" \
     "https://api.example.com/data"
# POST 요청
curl -X POST \
     -H "Content-Type: application/json" \
     -d '{"name":"John","email":"[email protected]"}' \
     "https://api.example.com/users"
# 응답 저장
curl -o response.json "https://api.example.com/data"
# 진행률 표시
curl --progress-bar -o large_file.zip "https://example.com/file.zip"

제한사항 및 주의사항

Rate Limiting 전략

flowchart TB
    Request[API 요청] --> Check{Rate Limit\n확인}
    
    Check -->|OK| Call[API 호출]
    Check -->|초과| Wait[대기]
    
    Call --> Success{성공?}
    Success -->|200| Cache[캐시 저장]
    Success -->|429| Retry["재시도\nExponential Backoff"]
    Success -->|5xx| Retry
    Success -->|4xx| Error[에러 처리]
    
    Wait --> Sleep[Sleep]
    Sleep --> Check
    
    Retry --> Sleep2[대기 시간 증가]
    Sleep2 --> Call
    
    Cache --> Return[결과 반환]
    Error --> Return

무료 플랜 제한 비교

일일 요청 제한:
  Alpha Vantage:    25 requests/day
  NewsAPI:          100 requests/day
  기상청:           1,000 requests/day
  Kakao Map:        300,000 requests/day
  
월별 요청 제한:
  OpenWeatherMap:   1,000,000 requests/month
  WeatherAPI:       1,000,000 requests/month
  ExchangeRate:     1,500 requests/month
  RAWG:             20,000 requests/month
  
시간당 요청 제한:
  Unsplash:         50 requests/hour
  Pexels:           200 requests/hour
  GitHub:           5,000 requests/hour (인증)
  
분당 요청 제한:
  OpenWeatherMap:   60 requests/min
  CoinGecko:        10-50 requests/min
  Reddit:           60 requests/min

정리

API 선택 체크리스트

프로젝트 요구사항:
  - [ ] 필요한 데이터 타입 확인
  - [ ] 예상 요청 횟수 계산
  - [ ] 상업적 사용 여부 확인
  - [ ] 응답 속도 요구사항
  - [ ] 데이터 신선도 (실시간 vs 캐싱)
API 평가:
  - [ ] 무료 제한 확인
  - [ ] 인증 방식 확인
  - [ ] 문서 품질 확인
  - [ ] 커뮤니티 활성도
  - [ ] 안정성 (SLA)
보안:
  - [ ] API 키 환경 변수 관리
  - [ ] .gitignore에 .env 추가
  - [ ] 프론트엔드는 백엔드 프록시 사용
  - [ ] HTTPS 사용
  - [ ] Rate limiting 구현
최적화:
  - [ ] 캐싱 구현
  - [ ] 에러 처리 및 재시도
  - [ ] 요청 배칭
  - [ ] 응답 압축

프로젝트 아이디어

초급:
  ✅ 날씨 앱 (OpenWeatherMap)
  ✅ 환율 계산기 (ExchangeRate-API)
  ✅ 영화 검색 (TMDB)
  ✅ 뉴스 리더 (NewsAPI)
중급:
  ✅ 암호화폐 트래커 (CoinGecko)
  ✅ 지도 기반 장소 검색 (Google Maps)
  ✅ 주식 차트 (Alpha Vantage)
  ✅ 이미지 갤러리 (Unsplash)
고급:
  ✅ AI 챗봇 (OpenAI)
  ✅ 영화 추천 시스템 (TMDB)
  ✅ 실시간 대시보드 (멀티 API)
  ✅ 데이터 시각화 (여러 API 통합)

핵심 팁

1. 무료 플랜 제한 확인
   → 일일/월별 요청 횟수 계산
2. API 키 보안
   → 환경 변수 사용, GitHub에 노출 금지
3. 캐싱 활용
   → 불필요한 요청 줄이기
4. 에러 처리
   → 429 (Rate Limit), 5xx (서버 오류) 재시도
5. 백엔드 프록시
   → 프론트엔드에서 API 키 숨기기
6. 약관 확인
   → 상업적 사용 제한 여부
7. 대체 API 준비
   → 메인 API 장애 시 폴백


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. 개인 프로젝트에 바로 사용할 수 있는 무료 공공 API 100개 이상. 날씨, 지도, 금융, AI, 이미지, 뉴스, 게임, 공공데이터까지. 인증 방법, 사용 예제, 제한사항 완벽 정리. Start now. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.

참고 자료

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「무료 API 완벽 가이드 | 개인 프로젝트용 공공 API 100+ 총정리」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「무료 API 완벽 가이드 | 개인 프로젝트용 공공 API 100+ 총정리」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

API, 무료API, 공공API, REST, OpenAPI, 개인프로젝트 등으로 검색하시면 이 글이 도움이 됩니다.