노션 Public API 연동하기

앞선 과정에서 public API를 등록했습니다. 이제 등록한 API를 이용해서 실제 DB와 상호작용 할 수 있도록 해야 합니다. 단순한 API 키 호출인 프라이빗 API에 비해, 퍼블릭 API는 OAuth 콜백을 통해 사용자의 code를 전달받고, 이를 ACCESS TOKEN으로 전환해서 사용해야 합니다.

토큰을 발급하는 과정을 수행해 보겠습니다.

1. Redirect URI 설정

로컬 환경의 redirect URI 설정

로컬환경에서 테스트하기 위해 http://localhost:8000/public-api/register 를 redirect URI로 등록합니다. redirect URI는 OAuth를 통한 인증 과정이 끝난 다음, 사용자를 보낼 페이지입니다.

2. 콜백 API 구현

이제, 실제로 OAuth 콜백 호출에 응답하기 위한 콜백 API를 구현합니다. 이 과정에서는 python의 fastapi 라이브러리를 사용합니다. 익숙한 다른 언어/프레임워크를 사용해도 아무 문제 없습니다.

폴더 구조

 append/
    append.py # API 기반 페이지 업데이트 코어 로직
 register/
    register.py # OAuth 콜백 코어 로직
 .env # 환경변수
 main.py # FastAPI 함수

폴더 구조에서 append/register/ 폴더 안에 코어 로직을 분리해 놓은 건 이 프로젝트가 실제로는 AWS Lambda로 배포될 예정이기 때문입니다.

.env

NOTION_CLIENT_ID=#########-####=####-############
NOTION_CLIENT_SECRET=secret_###########################################
REDIRECT_URI=http://localhost:8000/public-api/register

환경 변수를 입력해 줍니다. NOTION_CLIENT_IDNOTION_CLIENT_SECRET 은 API 통합의 OAuth 클라이언트 ID와 OAuth 클라이언트 시크릿을 입력해 줍니다. REDIRECT_URI 역시 앞에서 설정해 준 redirect URI와 일치하도록 입력해줍니다.

OAuth 클라이언트 ID, OAuth 클라이언트 시크릿을 입력

main.py

"""
FastAPI Wrapper, 코어 함수들을 로컬 환경에서 실행할 수 있도록 제공합니다
"""
from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI, APIRouter, Request
from fastapi.responses import HTMLResponse

app = FastAPI()

public_api_router = APIRouter(prefix="/public-api")

@public_api_router.get("/register")
def register(request: Request):
    code = request.query_params.get("code")
    access_token = get_access_token(code)
    if access_token:
        print(access_token) # 배포 시 ACCESS TOKEN 저장 코드로 대체
        return {"message": "퍼블릭 API 등록 성공"}
    return {"message": "퍼블릭 API 등록 실패"}

app.include_router(daily_quote_router)

public_api_router를 구현해 준 건, 실제 Lambda 배포 시 API Gateway와 형식을 맞추기 위한 것입니다. 로컬 테스트만을 진행하신다면 불필요한 부분입니다. 다만, 등록해 준 redirect URI와 일치시키기 위해 다음과 같이 코드가 수정되어야 합니다.

APIRouter 제외 버전
"""main.py"""
from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI, APIRouter, Request
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/public-api/register")
def register(request: Request):
    return {"message": "Registration successful"}

@app.post("/public-api/quote")
def quote():
    return {"message": "Quote retrieved"}
access_token이 노출되면 해당 사용자의 API가 연결된 모든 페이지에 대한 유효한 권한이 탈취당할 수 있기 때문에 관리에 각별히 주의하셔야 합니다. 이 코드는 로컬 환경에서의 public API 작동 테스트이기 때문에 이에 대한 별도의 고려 없이 print로 출력해 두겠습니다.

register.py

"""
Register API Core Logic

Notion Public API를 이용해 사용자 Access Token을 발급받습니다
"""
import requests
import os

NOTION_CLIENT_ID = os.getenv("NOTION_CLIENT_ID")
NOTION_CLIENT_SECRET = os.getenv("NOTION_CLIENT_SECRET")
REDIRECT_URI = os.getenv("REDIRECT_URI")

NOTION_OAUTH_URL = "https://api.notion.com/v1/oauth/token"

def get_access_token(code: str) -> str:
    """OAuth 콜백 함수로서 호출되어 인증 결과를 ACCESS_TOKEN으로 전환합니다"""
    headers = {
        "Accept": "application/json",
        "Content-Type": "application/json",
    }
    
    data = {
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": REDIRECT_URI,
    }

    auth = (NOTION_CLIENT_ID, NOTION_CLIENT_SECRET)

    res = requests.post(NOTION_OAUTH_URL, data=data, auth=(NOTION_CLIENT_ID, NOTION_CLIENT_SECRET))
    if res.status_code != 200:
        return ""
    token_data = res.json()
    access_token = token_data["access_token"]

    return access_token

3. 인증 URL 클릭

인증 URL을 이용하여 API 등록 실행

인증 URL을 이용해 연결 등록을 실행합니다. 인증 URL은 네 개의 쿼리 파라미터를 갖습니다.

  • client_id
  • response_type
  • owner
  • redirect_uri

이 중 신경써야 할 것은 redirect_uri 입니다. redirect URI를 여러 개 등록한 경우, 가장 먼저 등록된 redirect URI를 가리키는 인증 URL만 생성됩니다. 다른 redirect URI로 호출하고 싶다면, 이 부분을 바꿔서 링크를 걸어야 합니다.

인증 URL로 접속하면 API 인증 화면이 나타납니다

액세스 요청 팝업

페이지 선택을 클릭하면, 연결을 추가할 페이지를 선택하는 화면으로 넘어갑니다.

연결 추가 페이지 선택

Public API 연동 예제 라는 페이지를 만들어 연결해 보겠습니다. 액세스 허용 을 클릭합니다.

ntn_으로 시작하는 ACCESS TOKEN을 획득

4. notion_client로 페이지 제어

발급받은 토큰을 이용해 액세스 제어를 수행해 보겠습니다. 간단하게 지정된 페이지에 입력받은 텍스트를 저장하는 방식으로 진행합니다.

append.py

"""
Append API Core Logic

notion_client를 이용해 지정된 page에 문구를 삽입합니다.
"""
from notion_client import Client
from notion_client.errors import APIResponseError

def append_block(access_token: str, page_id: str, text: str) -> bool:
    """access_token, page_id, text를 입력받아 지정된 페이지에 텍스트를 삽입합니다"""
    try:
        client = Client(auth=access_token)
        client.blocks.children.append(block_id=page_id, children=[
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {
                    "rich_text": [
                        {
                            "type": "text",
                            "text": {
                                "content": text,
                            }
                        }
                    ]
                }
            }
        ])
        return True
        
    except APIResponseError as e:
        print(f"노션 API 에러: {e.code} - {e.message}")
        return False
    
    except Exception as e:
        print(f"노션 API 에러: {e}")
        return False

main.py

"""
FastAPI Wrapper, 코어 함수들을 로컬 환경에서 실행할 수 있도록 제공합니다
"""
from dotenv import load_dotenv
load_dotenv()

from fastapi import FastAPI, APIRouter, Request
from fastapi.responses import HTMLResponse

from register.register import get_access_token
from append.append import append_block

app = FastAPI()

public_api_router = APIRouter(prefix="/public-api")

@public_api_router.get("/register")
def register(request: Request):
    code = request.query_params.get("code")
    access_token = get_access_token(code)
    if access_token:
        print(access_token) # 배포 시 ACCESS TOKEN 저장 코드로 대체
        return {"message": "퍼블릭 API 등록 성공"}
    return {"message": "퍼블릭 API 등록 실패"}

@public_api_router.post("/append")
def append(request: Request, access_token: str, page_id: str, text: str):
    result = append_block(access_token, page_id, text)
    if result:
        return {"message": "페이지 삽입 성공"}
    return {"message": "페이지 삽입 실패"}

app.include_router(public_api_router)

API로 제어할 준비가 완료되었으니, localhost:8000/docs 로 접속해서 Try it out 버튼을 클릭하고 값들을 입력해 줍니다.

액세스 토큰, 페이지 ID, text를 입력

값을 입력하고 Execute 를 클릭해서 실제로 공유된 페이지에 텍스트가 추가된 것을 확인해 봅니다.

FastAPI의 성공 메시지

입력된 텍스트가 정상적으로 API를 통해 반영되었음

이상의 모든 코드는 github에서 참조하실 수 있습니다