# 메타모아 3D 광고 · metamoa 3D ads > AI가 읽고 바로 붙일 수 있게 쓴 안내예요. 사람용: https://metamoa.com/ads/docs · 운영자 API 전체: https://metamoa.com/api.md · OpenAPI: https://metamoa.com/api/openapi.json ## 한눈에 보기 메타모아 3D 광고는 광고주의 3D 모델·광고용 건물·광고 캐릭터를 웹 메타버스 공간 안 광고 자리에 띄우고, 사람이 실제로 본 것과 한 일만 세어 광고비를 받고 그 몫을 공간 운영자에게 나누는 광고예요. 광고업·광고대행업으로 대기업부터 중소기업까지 받아요. - 공간 운영자: 공간 관리(/owner)에서 바뀐 약관에 재동의 → 공간별 「광고 게재 동의」 → 스크립트 한 줄과 광고 자리를 넣으면 끝. - 광고주: /ads/manage에서 메타모아 계정으로 로그인 → 3D 모델(glb)을 올리거나, 광고용 건물을 골라 이미지·텍스처를 넣거나, 기본 광고 캐릭터에 소품과 광고를 고르기 → 노출 공간을 직접 고르거나 「추천으로」 → 과금 방식·예산·기간 → 검수 뒤 집행. - 모델이 없으면 「모델 제작 요청」을 고르면 우리가 만들어 드려요. 모델을 직접 주셔도 받아요. 기본 광고 캐릭터는 금액 상관없이 무료예요. - 과금은 넷: CPM(viewable 노출), CPA(모델 클릭·브랜드 공간 입장), 노출 시간(보인 누적 시간), 공간 고정 계약(기간제, 자리 독점). 금액은 모두 (공급가, 부가세 별도). - 광고 모델의 크기는 공간에 따라 운영자·메타모아가 조절할 수 있어요. ## 상품 4종과 과금 금액은 모두 공급가, 부가세 별도예요. | 상품 | billing | 세는 것 | 단가 | | --- | --- | --- | --- | | CPM (viewable 노출) | `cpm` | viewable 노출 1회 = 모델이 화면 안에 일정 몫 이상, 정해진 시간 넘게 이어서 보임(탭이 보이는 상태) | viewable 1,000회당 8,000원 | | CPA · 모델 클릭 | `cpa` | 광고 모델을 탭·클릭. 클릭한 뒤 브랜드 공간 입장까지 가면 입장 1건만 청구 | 클릭 1회당 700원 | | CPA · 브랜드 공간 입장 | `cpa` | 광고를 눌러 브랜드 공간(brand_url)으로 들어감. 서버 리다이렉트(/ads/go)로 세고, 같은 사람·캠페인은 하루 한 번 | 입장 1회당 1,000원 | | 노출 시간 | `time` | viewable 노출 뒤 광고가 보인 누적 시간(입력 활동이 있을 때만, 한 사람·모델 하루 상한까지) | viewable 1,000초당 1,000원 | | 공간 고정 계약 | `fixed` | 기간제(최대 한 달). 계약한 광고 자리를 그 기간 동안 혼자 씀(자리 단위). 노출 수는 보장하지 않음 | 아래 등급표 | #### 공간 고정 계약 등급 등급은 그 자리가 있는 공간의 최근 14일 평균 하루 공간 클릭으로 정해요. 가장 낮은 등급에 못 미치는 공간의 자리는 공간 고정 계약으로 팔지 않고 성과형으로만 받아요. 계약 기간은 주간 7일 · 월간 30일 중 하나예요. | 등급 | 하루 공간 클릭 | 주간(자리 1개) | 월간(자리 1개) | | --- | --- | --- | --- | | S | 1,000 이상 | 50,000원 | 180,000원 | | A | 300~999 | 15,000원 | 50,000원 | | B | 100~299 | 5,000원 | 18,000원 | - 최소 집행: 매체비 200,000원 (공급가, 부가세 별도), 모델 제작비 제외. 성과형은 예산, 공간 고정 계약은 계약 금액 합으로 봐요. - 성과형(CPM·CPA·노출 시간)은 실제로 센 만큼만 청구하고, 쓴 금액이 예산(budget_won)에 닿으면 그 캠페인은 더 나가지 않아요. - CPM 계약은 고른 자리의 예상 viewable 노출(하루 공간 클릭 × 0.7 × 일수)의 70%까지만 받아요(409 over_inventory). - 공간 고정 계약이 잡힌 자리에는 그 기간 동안 그 캠페인 하나만 나가고, 같은 자리·겹치는 기간의 고정 계약은 받지 않아요(409 slot_taken). 자리는 검수 승인 때 잡혀요: 검수 대기 중인 신청은 자리를 묶지 않고, 먼저 승인된 계약이 이겨요. 모델이 안 뜬 날이 있거나 트래픽이 30% 넘게 떨어지면 일할 환불이나 연장으로 맞춰요. - 청구와 등급은 메타모아 실측 리포트(viewable 노출·클릭·입장·보인 시간, 공간 클릭) 기준이에요. ## 집계 공식 날짜는 한국 시간 하루 단위(ad_daily). 금액은 원 단위 아래를 버린 공급가예요. 규칙에 걸린 이벤트는 세지 않고 이유별로 따로 세요(ad_excluded). - viewable 노출 imps = 규칙을 모두 통과한 `imp` 이벤트 수 - 클릭 clicks = 통과한 `click` 수, 입장 enters = 통과한 입장(/ads/go) 수, converted = 클릭한 뒤 같은 토큰으로 입장까지 간 수 - 보인 시간 view_ms = 통과한 `time` 이벤트 ms 합(노출이 센 토큰만, 이벤트마다 15,000ms까지, 한 사람·모델 하루 상한까지) - CPM 금액 = floor(imps × cpm_won ÷ 1,000) - CPA 금액 = (clicks − converted) × click_won + enters × enter_won - 노출 시간 금액 = floor(view_ms ÷ 1,000 × time_1000s_won ÷ 1,000) - 공간 고정 계약 금액 = 계약한 자리마다 등급 단가의 합. 기간 일부만 집계할 때는 자리마다 floor(단가 × 그 기간 안의 계약 일수 ÷ 계약 일수) - 운영자 몫 = floor(공간별 금액 × operator_share_pct ÷ 100). 금액은 무효 트래픽을 뺀 매체 공급가이고, 모델·공간 제작비는 나누지 않아요. 운영자 배분 비율: 매체 공급가의 60% ## 약관 재동의와 광고 게재 동의 이용약관에 광고 게재 조항(제4조의4)이 들어갔어요. 광고를 받으려면 운영자가 공간 관리 화면에서 두 가지를 해요. 1. 재동의: 공간 관리(/owner?id=<공간 id>)에 「약관이 광고 게재가 가능하도록 업데이트되었어요」 안내와 「재동의」 버튼이 보여요. 재동의는 공간 관리 화면에서만 돼요(공간 키로는 403 owner_page_only). 2. 광고 게재 동의: 재동의 뒤 같은 화면에서 공간별로 켜고 꺼요. 켜야 그 공간의 광고 API(스크립트 한 줄·광고 키)가 보이고, 끄면 바로 광고가 나가지 않아요(serve 403 ads_off). 동의 기록: 공간, 누가(그 공간 관리에 로그인한 운영자 메일의 해시), 언제, 어느 약관 판(terms.html의 data-ads-version), 켬·끔을 지우지 않고 쌓아요(ad_consents). 재동의하지 않았거나 광고 게재 동의를 끈 공간, 목록에 오르지 않은 공간에는 광고가 나가지 않아요. ## 광고 자리(슬롯) 운영자가 자기 메타버스 안에 광고가 설 자리를 직접 정해요. 광고주는 자리 단위로 고르거나 추천을 받고, 공간 고정 계약도 자리 단위로 묶어요. 자리 목록은 공간 관리 화면에서 보고 켜고 끌 수 있어요. | 칸 | 형식 | 뜻 | | --- | --- | --- | | name | 1~40자 | 공간 안에서 겹치지 않는 이름 | | kind | `floor` · `wall` · `air` · `character` | 바닥 모델 · 벽 간판 · 공중 배너 · 서 있거나 걸어 다니는 광고 캐릭터 | | position | [x, y, z] 미터 | 자리 박스 바닥 가운데의 위치(공간 좌표) | | rotation | [x, y, z] 도(-360~360) | 자리의 회전. 없으면 [0, 0, 0] | | size | [가로, 높이, 깊이] 미터(0.1~200) | 광고가 들어갈 박스. 광고는 이 박스에 맞게 키우거나 줄여요 | | scale | [최소, 최대] 배(0.05~20) | 공간별 크기 배율: 박스에 맞출 때 광고 원래 크기의 이 배율 안에서만 키우고 줄여요. 없으면 [0.25, 4] | | roam | 0~50 미터 | character 자리: 캐릭터가 걸어 다니는 반지름(0이면 서 있음) | | industries | 업종 id 목록 | 이 자리가 받는 업종. 비우면 모든 업종 | | enabled | true · false | 끄면 이 자리에는 광고가 나가지 않아요 | 광고 모델의 크기는 공간에 따라 운영자·메타모아가 조절할 수 있어요. 캐릭터 광고는 character 자리에만, 모델·건물 광고는 floor·wall·air 자리에만 나가요. SDK(공개 광고 키)로는 새 자리만 만들 수 있고, 그렇게 만든 자리는 꺼진 채로 들어가 운영자가 공간 관리에서 확인하고 켜야 광고가 나가요. 이미 있는 자리(같은 이름)는 SDK가 바꾸지 못하고 그대로 돌려줘요. 자리의 박스·종류·업종을 바꾸고 켜고 끄는 건 공간 관리 로그인이나 공간 키로만 돼요. 업종 id: game(게임) · fashion(패션·뷰티) · food(식음료) · tech(IT·전자) · finance(금융) · edu(교육) · travel(여행·레저) · culture(문화·엔터) · auto(자동차) · public(공공·비영리) · etc(기타) 공간마다 자리 30개까지. 자리 API는 공간 키(mm_live_)나 공간 관리 로그인으로 써요: ``` GET https://metamoa.com/api/space/{space_id}/ads/slots POST https://metamoa.com/api/space/{space_id}/ads/slots {"name":"plaza-front","kind":"floor","position":[0,0,-8],"rotation":[0,90,0],"size":[4,4,4],"scale":[0.5,2],"industries":["game","food"]} POST https://metamoa.com/api/space/{space_id}/ads/slots {"name":"gate-greeter","kind":"character","position":[3,0,-4],"size":[1.2,2.2,1.2],"roam":3} PATCH https://metamoa.com/api/space/{space_id}/ads/slots/{slot_id} {"enabled": false, "scale": [0.5, 1.5]} DELETE https://metamoa.com/api/space/{space_id}/ads/slots/{slot_id} ``` ## 운영자: 붙이는 순서 1. 공간 관리(https://metamoa.com/owner?id=<공간 id>)에서 「재동의」 → 「광고 게재 동의」를 켜요. 2. 같은 화면 「광고 API」 칸의 스크립트 한 줄을 공간 페이지
나 끝에 넣어요. 광고 키(mm_adpub_)는 공개해도 되는 키예요(공간 키 mm_live_는 넣지 마세요). 3. three.js 공간이면 장면이 만들어진 뒤 attach와 slot을 불러요. 자리는 처음 부를 때 꺼진 채로 등록돼요. 공간 관리 「광고 자리」에서 켜면 광고가 나가요(자리 API로 처음부터 켠 채 만들어도 돼요). 4. 광고가 보이면 끝이에요. viewable 노출·클릭·보인 시간은 SDK가 알아서 보내고, 브랜드 공간 입장은 광고를 누르면 SDK가 /ads/go로 열어 서버가 세요. 5. 공간 관리의 「광고 자리」에서 자리를 켜고 끄고, 30일 집계(규칙 적용 전·후)와 배분 몫을 봐요(GET /api/space/{space_id}/ads/stats). ``` ``` ``` import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; // after your scene, camera and renderer exist: (window.metamoaAdsReady = window.metamoaAdsReady || []).push((ads) => { ads.attach({ THREE, GLTFLoader, scene, camera, renderer }); ads.slot({ name: 'plaza-front', kind: 'floor', position: [0, 0, -8], rotation: [0, 0, 0], size: [4, 4, 4] }); ads.slot({ name: 'north-wall', kind: 'wall', position: [0, 3, -15], size: [6, 3.4, 0.4] }); ads.slot({ name: 'gate-greeter', kind: 'character', position: [3, 0, -4], size: [1.2, 2.2, 1.2], roam: 3 }); }); ``` three.js가 아닌 엔진(Babylon.js, PlayCanvas, Unity WebGL 등)은 serve로 광고를 받아 직접 그리고 이벤트를 보내요: `ads.serve(name)` → { ad }, `ads.report(name, event)`. 입장은 ad.enter_url을 새 창이나 같은 창으로 열면 돼요. 규격은 아래 「이벤트 규격」. ## SDK가 세는 방법 - viewable 노출: 모델의 정해진 몫 이상이 카메라 화면 안에 있고, 화면에 비친 크기가 정해진 넓이 이상인 채로, 탭이 보이는 동안 정해진 시간 넘게 이어지면 노출 1회를 보내요(serve 토큰 하나에 한 번). - 보인 시간: 노출된 뒤 광고가 위 조건으로 보이는 동안 5초마다 쌓아 보내고, 그 사이 카메라·포인터 입력 수를 같이 보내요(탭이 숨으면 멈춤). - serve를 부를 때 vid를 같이 보내요(GET /ads/v1/serve?…&vid=). 서버가 그 사람을 토큰 안에 서명해 넣어요. - 클릭: 광고를 탭·클릭하면 `click`을 보내고, 광고에 브랜드 공간이 있으면 enter_url(/ads/go)을 새 창으로 열어요. 서버가 입장을 세고 브랜드 공간으로 보내요. - 브라우저마다 무작위 id(vid, localStorage)를 하나 만들어 반복 제한에 써요. 이름·연락처 같은 개인 정보는 보내지 않아요. - 광고는 30분마다 새로 받아요(새 토큰). 캐릭터 자리는 roam 반지름 안을 천천히 걸어요. ## 봇·부정 검증 규칙 이벤트는 서버에서 아래 규칙을 차례로 거쳐요. 하나라도 걸리면 집계에서 빼고 그 규칙 이름으로 따로 세요. 규칙은 data/ads-config.json의 bot_rules에서 하나씩 켜고 꺼요. | 규칙 | 하는 일 | 지금 | | --- | --- | --- | | 봇 UA (`ua_bot`) | 검색 로봇·미리보기·스크립트(curl, python, headless 등)와 User-Agent가 없는 요청은 뺀다. | 켬 | | 자동화 브라우저 (`headless`) | navigator.webdriver가 켜진 브라우저, HeadlessChrome·PhantomJS·Electron·jsdom, Accept-Language가 없는 요청은 뺀다. | 켬 | | 탭 가시성·포커스 (`visibility_focus`) | 노출은 탭이 보이고(document.visibilityState = visible) 창에 포커스가 있을 때만, 보인 시간은 탭이 보일 때만 센다. | 켬 | | viewable 판정 (`min_view`) | 모델의 정해진 몫 이상이 화면 안에, 정해진 넓이 이상으로, 정해진 시간 넘게 이어져야 노출 1회. | 켬 | | 같은 사람·IP 반복 제한 (`repeat_limit`) | 같은 사람(브라우저 id, 없으면 IP)·같은 모델의 노출과 클릭은 정해진 시간에 한 번, 입장은 하루에 한 번. 한 IP의 시간당 상한을 넘는 것은 뺀다. | 켬 | | 서버 서명 토큰 (`signed_token`) | 이벤트는 serve가 준 서명 토큰(HMAC)을 들고 와야 한다. 보는 사람(브라우저 id, 없으면 IP의 해시)이 토큰 안에 서명돼 이벤트에서 바꿀 수 없고, 토큰은 유효 시간 안에 노출·클릭·입장 각 한 번만 센다. 보인 시간·클릭은 노출을 센 IP에서 온 것만. 서명이 틀린 토큰과 같은 토큰의 두 번째 노출은 규칙을 꺼도 늘 뺀다. | 켬 | | 노출 시간 측정 (`view_time`) | 보인 시간은 그 사이에 카메라·포인터 입력이 있을 때만, 토큰마다·한 사람과 모델마다 하루·한 IP와 모델마다 하루 상한까지만 센다. | 켬 | | 운영자 본인 트래픽 (`owner_traffic`) | 그 공간의 공간 관리 화면을 연 IP에서 온 이벤트는 정해진 날 동안 뺀다. | 켬 | | 사람 입력 흔적 (`human_input`) | 노출 동안 카메라·포인터 움직임이 정해진 수 이상 있어야 노출로, 클릭은 브라우저가 사람 입력이라고 표시한(isTrusted) 것만 센다. | 끔 | | 이상치 감시 (`anomaly`) | 한 사람이 하루에 정해진 수를 넘게 본 노출은 빼고, 클릭 비율이 네트워크 평균의 정해진 배를 넘는 공간은 지급을 보류하고 살펴본다. | 켬 | - 클릭과 입장은 그 토큰의 노출이 먼저 세어진 경우에만 세요(아니면 no_impression). - 서버가 기억하는 표가 가득 차면 아직 기간이 남은 기록을 지우지 않고, 새 이벤트를 세지 않아요(capacity). 막는 쪽으로 닫혀요. ## 이벤트 규격 SDK 요청은 그 공간 사이트에서만 받아요. 운영자 자기 도메인이면 같은 등록 도메인, github.io·vercel.app 같은 공용 호스트면 호스트 이름이 정확히 같아야 하고, 경로 아래에 있는 공간(github.io/<저장소>, claude.ai)은 Referer 경로까지 그 아래여야 해요(SDK는 전체 Referer를 보내요). 모두 CORS 열림, JSON. sendBeacon(text/plain)도 받아요. #### 광고 받기 · serve ``` GET https://metamoa.com/ads/v1/serve?space={space_id}&key={mm_adpub_…}&slot={자리 이름 또는 id}&vid={vid} → 200 { "space_id": "onsen-village", "slot": { "id": "sl_k2m9x0a1b2c3d4e5", "name": "plaza-front", "kind": "floor", "position": [ 0, 0, -8 ], "rotation": [ 0, 0, 0 ], "size": [ 4, 4, 4 ], "scale": [ 0.25, 4 ], "roam": 0 }, "ad": { "campaign_id": "cp_q8w7e6r5t4y3u2i1", "brand": "반짝 우유", "token": "