# 메타모아 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": "", "enter_url": "https://metamoa.com/ads/go?t=", "kind": "building", "building_id": "box-kiosk", "model_url": "https://metamoa.com/ads/buildings/box-kiosk.glb?v=1", "footprint": [ 4, 4.2, 4 ], "mode": "image", "faces": [ { "id": "sign", "mesh": "ad_sign", "image_url": "https://metamoa.com/ads/a/as_a1b2c3d4e5f6g7h8.webp" } ] } } ``` ad가 null이면 지금 그 자리에 나갈 광고가 없어요. kind가 model이면 model_url의 glb를, building이면 건물 glb를 놓은 뒤 mode가 image면 faces의 mesh 이름에 image_url 이미지를, texture면 texture.meshes 전체에 image_url을 repeat만큼 반복해 입혀요. character면 캐릭터 glb를 세우고 prop.attach 자리에 소품을 붙여 그 판에 image_url 이미지나 text 글자를 넣어요. 모두 slot.size 박스에 맞추되 원래 크기의 slot.scale 배율 안에서만 키우고 줄여요. #### 자리 등록 · slots ``` POST https://metamoa.com/ads/v1/slots { "space": "{space_id}", "key": "{mm_adpub_…}", "name": "plaza-front", "kind": "floor", "position": [ 0, 0, -8 ], "rotation": [ 0, 90, 0 ], "size": [ 4, 4, 4 ], "scale": [ 0.5, 2 ], "industries": [ "game", "food" ] } → 201 { "slot": {…, "enabled": false} } (새 자리, 운영자가 켤 때까지 꺼짐) · 200 (같은 이름의 자리를 바꾸지 않고 그대로) ``` #### 이벤트 보내기 · events ``` POST https://metamoa.com/ads/v1/events { "space": "onsen-village", "key": "mm_adpub_XXXXXXXXXXXXXXXXXXXXXX", "vid": "b3f1c9a2e8d74a6f9c1b2a3d", "events": [ { "type": "imp", "token": "", "ms": 2100, "area": 0.04, "inside": 1, "vis": true, "focus": true, "inputs": 3, "wd": false }, { "type": "time", "token": "", "ms": 5000, "vis": true, "inputs": 2, "wd": false }, { "type": "click", "token": "", "trusted": true, "wd": false } ] } → 200 { "accepted": 3, "excluded": 0 } ``` | type | 칸 | 뜻 | | --- | --- | --- | | imp | token, ms, area, inside, vis, focus, inputs, wd | ms: 이어서 보인 시간, area: 화면 넓이 대비 모델 넓이(0~1), inside: 모델 중 화면 안 몫(0~1), vis·focus: 탭 보임·창 포커스, inputs: 그동안 카메라·포인터 입력 수, wd: navigator.webdriver | | time | token, ms, vis, inputs, wd | 지난 time 뒤로 보인 시간(15,000ms까지)과 그 사이 입력 수 | | click | token, trusted, wd | 광고를 탭·클릭함. trusted: 사람 입력(isTrusted) | 한 번에 20개까지. vid: 브라우저마다 16~64자 무작위 id(serve에 보낸 것). 이벤트의 보는 사람은 토큰 안의 값을 써요. 틀린 이벤트·남의 공간 토큰은 excluded로만 세요. #### 입장 · go ``` GET https://metamoa.com/ads/go?t={serve 토큰} → 302 Location: {brand_url} (노출이 센 토큰이면 입장을 센 뒤 브랜드 공간으로) ``` ## 광고용 건물·캐릭터와 업로드 규격 모델이 없는 광고주는 우리가 주는 광고용 건물이나 기본 광고 캐릭터를 골라요. 건물은 두 방식 중 하나를 골라요: 이미지형(정해진 판에 이미지 한 장씩) · 텍스처형(건물 표면 전체에 텍스처·패턴 한 장). | 건물 | 이미지형: 이미지 넣는 곳 | 텍스처형 | | --- | --- | --- | | 상자 가판대 (`box-kiosk`) | 정면 간판(`sign`) 16:9, 1280×720px 이상 | 1:1, 512×512px 이상, 2×2 반복 | 기본 광고 캐릭터는 금액 상관없이 무료예요. character 자리에 서거나 걸어 다니며, 목걸이 팻말·손 피켓·풍선·깃발·쇼핑백 같은 소품을 들고, 소품 판에 광고 이미지나 간판 글자(12자까지)가 들어가요. | 캐릭터 | id | 키 | 출처 | | --- | --- | --- | --- | | 야영지 방장 | `camp-bangjang` | 1.8m | 모아 야영지 캐릭터 · KayKit(CC0) 바탕, 플로트 주식회사 | | 야영지 창모아 | `camp-changmoa` | 1.8m | 모아 야영지 캐릭터 · KayKit(CC0) 바탕, 플로트 주식회사 | | 야영지 링크블린 | `camp-linkblin` | 1.6m | 모아 야영지 캐릭터 · Quaternius(CC0) 바탕, 플로트 주식회사 | | 빈센트 | `vincent` | 1.8m | 플로트 주식회사 | | 냥줍고 고양이 | `cat-short` | 0.6m | Cat by Polyfork (https://polyfork.dev/asset/cat-base), CC BY 4.0, 플로트 주식회사가 고침 | | 소품 | id | 이미지 비율·최소 크기 | | --- | --- | --- | | 목걸이 팻말 | `necklace` | 1:1, 512×512px 이상 | | 손 피켓 | `picket` | 4:3, 800×600px 이상 | | 풍선 | `balloon` | 1:1, 512×512px 이상 | | 깃발 | `flag` | 3:2, 600×400px 이상 | | 쇼핑백 | `bag` | 3:4, 600×800px 이상 | - 이미지 형식: PNG · JPEG · WEBP, 2MB까지, 가로·세로 4096px까지, 비율 오차 2%까지. 올린 이미지는 WebP로 다시 저장해요(사진 정보는 지워져요). - 3D 모델: glTF 2.0 바이너리(.glb) 한 파일, 6MB까지, 텍스처는 파일 안에, 바깥 주소(uri) 없이. - 올린 파일은 내용(매직 바이트)으로 형식을 확인해요: 이미지는 PNG·JPEG·WebP만(SVG·HTML 등은 받지 않음) 받아 WebP로 다시 만들고, 모델은 glTF 2.0 바이너리만 받아요. 광고 파일은 고정 형식(image/webp, model/gltf-binary)·nosniff·sandbox 정책으로만 내보내 스크립트로 실행되지 않아요. - 광고 모델의 크기는 공간에 따라 운영자·메타모아가 조절할 수 있어요. - 진짜 건물·캐릭터·소품은 data/ads-buildings.json 항목과 glb만 바꿔 끼워요(건물은 image.faces의 mesh와 texture.meshes, 소품은 attach.node·face.mesh가 glb 안 노드 이름). ## 모델 제작 요청 광고주가 「모델 제작 요청」을 고르면 우리가 3D 모델을 만들어 캠페인에 붙인 뒤 검수해요. 만들 자료(제품 사진·로고·도면)는 contact@float.do로 캠페인 번호와 함께 보내 주세요. | 등급 | grade | 제작비 | 이 매체비 이상 집행하면 무료 | | --- | --- | --- | --- | | 단순 제품·간판 | `simple` | 50,000원 (공급가, 부가세 별도) | 500,000원 | | 정밀 제품 | `precise` | 100,000원 (공급가, 부가세 별도) | 1,000,000원 | | 새 캐릭터 | `character` | 100,000원 (공급가, 부가세 별도) | 1,000,000원 | - 무료 기준은 실제 집행 매체비(계약 전체 기간 합, 최대 3개월, 모델링·부가세·무효 트래픽 제외)로 봐요. - 무료로 만든 모델은 메타모아 송출용 사용권만 드리고(저작권은 메타모아, 양도는 제작비를 받고), 수정은 1회예요. 광고주 사정으로 해지·감액하면 제작비를 비례로 청구해요: 제작비 × (1 − 실제 집행 매체비 ÷ 무료 기준). 유료 모델은 수정 2회예요. - 기본 광고 캐릭터(모아 야영지·냥줍고 캐릭터)는 제작 요청 없이 금액 상관없이 무료로 써요. ## 광고주: 셀프 등록 순서 1. https://metamoa.com/ads/manage에서 메타모아 계정으로 로그인(가입도 같은 창). 2. 캠페인 만들기: 이름·브랜드·업종, 브랜드 공간 주소(입장 CPA를 쓸 때). 3. 광고물: ① 내 3D 모델(glb) 올리기 ② 광고용 건물 고르기 → 이미지형이면 판마다 이미지, 텍스처형이면 텍스처 한 장 ③ 기본 광고 캐릭터 + 소품 고르기 → 소품 판에 이미지나 간판 글자 ④ 모델 제작 요청(등급 선택). 규격과 미리보기가 보여요. 4. 노출 공간: 광고 게재에 동의한 공간 목록에서 분류·지금 접속자 수·하루 공간 클릭을 보고 자리를 직접 고르거나, 「추천으로」를 누르면 업종·분류·트래픽을 맞춘 공간 묶음을 추천해요. 5. 과금 방식(CPM·CPA·노출 시간·공간 고정 계약)·예산·기간을 정하고 신청 → 검수 대기. 6. 검수(음란·폭력·저작권 의심 등)를 통과하면 집행돼요. 진행 중인 캠페인은 멈추고 다시 켤 수 있고, viewable 노출·클릭·입장·보인 시간을 날마다 봐요. ``` PUT https://metamoa.com/api/ads/assets?kind=image&building=box-kiosk&face=sign (본문: 이미지 파일) PUT https://metamoa.com/api/ads/assets?kind=image&prop=necklace (본문: 이미지 파일) POST https://metamoa.com/api/ads/recommend {"industry": "food", "count": 5} POST https://metamoa.com/api/ads/campaigns { "name": "겨울 우유 3D", "brand": "반짝 우유", "industry": "food", "billing": "cpm", "budget_won": 500000, "start_day": "2026-12-01", "end_day": "2026-12-31", "targeting": "recommend", "slot_ids": [ "sl_k2m9x0a1b2c3d4e5" ], "brand_url": "https://milk.example/space", "creative": { "kind": "building", "building_id": "box-kiosk", "mode": "image", "images": { "sign": "as_a1b2c3d4e5f6g7h8" } } } (헤더: X-Account-Token: mm1.… · X-Device-Id: <기기 id>) ``` 캐릭터 광고물: {"kind": "character", "character_id": "camp-bangjang", "prop": "necklace", "image": "as_…"} 또는 "image" 대신 "text": "반짝 우유". 공간 고정 계약: "billing": "fixed", "fixed_period": "week" | "month", end_day = start_day + 기간 − 1. 모델 제작 요청: {"kind": "request", "grade": "simple", "note": "…"}. ## 검수 - 올라온 모델·이미지와 캠페인은 게재 전에 검수 대기(review)예요. 관리자가 승인하면 집행(active), 반려하면 rejected와 이유가 남아요. - 이미지는 올릴 때 AI 이미지 검사로 음란·폭력·남의 로고·캐릭터를 먼저 보고, 걸리면 바로 막아요(rejected). 검사를 못 하면 관리자가 직접 봐요. - 모델은 관리자가 직접 열어 봐요. 음란·폭력·저작권 의심이면 반려해요. ## 오류 코드 오류 본문은 { "error": "<코드>", "message": "<다음에 할 일 한 줄>" }이고, 칸 오류는 fields, 한도는 retry_after가 같이 와요. | HTTP | error | 뜻 | | --- | --- | --- | | 400 | `invalid` | 칸 형식이 틀림. fields에 틀린 칸이 온다. | | 400 | `bad_model` | glb가 아님, glTF 2.0이 아님, 바깥 주소(uri)가 들어 있음. reason에 이유. | | 400 | `bad_image` | 이미지 형식·크기·비율이 규격과 다름. reason(format·bytes·too_large·too_small·aspect)과 spec이 온다. | | 400 | `slot_unavailable` | 고른 자리가 광고를 받지 않거나, 그 업종을 받지 않거나, 광고물 종류와 맞지 않음(캐릭터는 캐릭터 자리에만). slot_ids에 그 자리. | | 401 | `unauthorized` | 공간 키·공간 관리 로그인이 없거나 틀림. | | 401 | `sign_in` | 광고주: 메타모아 계정 로그인이 없거나 끝남. | | 403 | `owner_required` | 공간 키는 있지만 운영자 확인 전. | | 403 | `owner_page_only` | 재동의·광고 게재 동의를 공간 키로 시도함(공간 관리 화면에서만 된다). | | 403 | `bad_ad_key` | SDK: space와 key(mm_adpub_)가 맞지 않음. | | 403 | `terms_required` | 그 공간이 바뀐 약관에 재동의하지 않음. | | 403 | `ads_off` | 그 공간이 광고 게재 동의를 끔. | | 403 | `origin_mismatch` | SDK 요청이 그 공간 사이트(같은 도메인)에서 오지 않음. | | 403 | `not_yours` | 광고주: 남의 파일 id를 씀. | | 404 | `no_slot` | 그 이름·id의 광고 자리가 없음. | | 404 | `not_found` | 그 캠페인·파일이 없음. | | 409 | `not_listed` | 목록에 오르지 않은(승인 전·숨김) 공간. | | 409 | `name_taken` | 같은 이름의 자리가 이미 있음. | | 409 | `slot_limit` | 공간마다 자리 30개까지. | | 409 | `slot_taken` | 그 기간에 다른 공간 고정 계약이 잡힌 자리. | | 409 | `tier_too_low` | 하루 공간 클릭이 가장 낮은 등급에 못 미치는 공간의 자리를 공간 고정 계약으로 고름. slot_ids에 그 자리. | | 409 | `not_for_sale` | 공간 고정 계약 단가가 없음. | | 409 | `under_minimum` | 최소 집행 매체비보다 적음. min_spend_won, spend_won이 온다. | | 409 | `over_inventory` | CPM 예산이 고른 자리의 예상 viewable 노출 상한을 넘음. max_budget_won이 온다. | | 409 | `rejected_file` | 검수에서 막힌 파일을 캠페인에 씀. | | 409 | `wrong_state` | 그 상태에서 못 하는 일(검수 중 캠페인 멈추기 등). | | 409 | `model_missing` | 관리자: 모델 제작 요청 캠페인에 모델을 붙이기 전에 승인함. | | 413 | `model_too_big` | 모델 파일이 6MB를 넘음. | | 429 | `too_many` | 요청이 너무 많음. Retry-After 초만큼 기다린다. | ## 전체 경로 ``` SDK (CORS *, 그 공간 사이트에서) GET /ads/sdk.js · GET /ads/v1/serve · POST /ads/v1/slots · POST /ads/v1/events · GET /ads/go · GET /ads/v1/buildings · GET /ads/a/{asset}.glb|webp 운영자 (공간 관리 로그인 mm_own_ 또는 공간 키 mm_live_) GET /api/space/{id}/ads · POST /api/space/{id}/ads/terms · POST /api/space/{id}/ads/consent {"on": true} (이 둘은 공간 관리 로그인만) GET|POST /api/space/{id}/ads/slots · PATCH|DELETE /api/space/{id}/ads/slots/{slot_id} · GET /api/space/{id}/ads/stats?from=&to= 광고주 (메타모아 계정: X-Account-Token + X-Device-Id) GET /api/ads/options · GET /api/ads/spaces · POST /api/ads/recommend · PUT /api/ads/assets · GET /api/ads/assets · GET /api/ads/assets/{id}/raw GET|POST /api/ads/campaigns · GET /api/ads/campaigns/{id} · POST /api/ads/campaigns/{id}/pause|resume 문서: /ads/docs (사람) · /ads/llms.txt (AI) · /api#ads · /api/openapi.json ```