메타모아 3D 광고 · 3D ads
한눈에 보기
메타모아 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)이 들어갔어요. 광고를 받으려면 운영자가 공간 관리 화면에서 두 가지를 해요.
- 재동의: 공간 관리(/owner?id=<공간 id>)에 「약관이 광고 게재가 가능하도록 업데이트되었어요」 안내와 「재동의」 버튼이 보여요. 재동의는 공간 관리 화면에서만 돼요(공간 키로는 403 owner_page_only).
- 광고 게재 동의: 재동의 뒤 같은 화면에서 공간별로 켜고 꺼요. 켜야 그 공간의 광고 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}운영자: 붙이는 순서
- 공간 관리(https://metamoa.com/owner?id=<공간 id>)에서 「재동의」 → 「광고 게재 동의」를 켜요.
- 같은 화면 「광고 API」 칸의 스크립트 한 줄을 공간 페이지 <head>나 <body> 끝에 넣어요. 광고 키(mm_adpub_)는 공개해도 되는 키예요(공간 키 mm_live_는 넣지 마세요).
- three.js 공간이면 장면이 만들어진 뒤 attach와 slot을 불러요. 자리는 처음 부를 때 꺼진 채로 등록돼요. 공간 관리 「광고 자리」에서 켜면 광고가 나가요(자리 API로 처음부터 켠 채 만들어도 돼요).
- 광고가 보이면 끝이에요. viewable 노출·클릭·보인 시간은 SDK가 알아서 보내고, 브랜드 공간 입장은 광고를 누르면 SDK가 /ads/go로 열어 서버가 세요.
- 공간 관리의 「광고 자리」에서 자리를 켜고 끄고, 30일 집계(규칙 적용 전·후)와 배분 몫을 봐요(GET /api/space/{space_id}/ads/stats).
<script src="https://metamoa.com/ads/sdk.js" data-space="{space_id}" data-key="{mm_adpub_…}" async></script>
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": "<serve 토큰>",
"enter_url": "https://metamoa.com/ads/go?t=<serve 토큰>",
"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": "<serve 토큰>",
"ms": 2100,
"area": 0.04,
"inside": 1,
"vis": true,
"focus": true,
"inputs": 3,
"wd": false
},
{
"type": "time",
"token": "<serve 토큰>",
"ms": 5000,
"vis": true,
"inputs": 2,
"wd": false
},
{
"type": "click",
"token": "<serve 토큰>",
"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 모델을 만들어 캠페인에 붙인 뒤 검수해요. 만들 자료(제품 사진·로고·도면)는 [email protected]로 캠페인 번호와 함께 보내 주세요.
| 등급 | grade | 제작비 | 이 매체비 이상 집행하면 무료 |
|---|---|---|---|
| 단순 제품·간판 | simple | 50,000원 (공급가, 부가세 별도) | 500,000원 |
| 정밀 제품 | precise | 100,000원 (공급가, 부가세 별도) | 1,000,000원 |
| 새 캐릭터 | character | 100,000원 (공급가, 부가세 별도) | 1,000,000원 |
- 무료 기준은 실제 집행 매체비(계약 전체 기간 합, 최대 3개월, 모델링·부가세·무효 트래픽 제외)로 봐요.
- 무료로 만든 모델은 메타모아 송출용 사용권만 드리고(저작권은 메타모아, 양도는 제작비를 받고), 수정은 1회예요. 광고주 사정으로 해지·감액하면 제작비를 비례로 청구해요: 제작비 × (1 − 실제 집행 매체비 ÷ 무료 기준). 유료 모델은 수정 2회예요.
- 기본 광고 캐릭터(모아 야영지·냥줍고 캐릭터)는 제작 요청 없이 금액 상관없이 무료로 써요.
광고주: 셀프 등록 순서
- https://metamoa.com/ads/manage에서 메타모아 계정으로 로그인(가입도 같은 창).
- 캠페인 만들기: 이름·브랜드·업종, 브랜드 공간 주소(입장 CPA를 쓸 때).
- 광고물: ① 내 3D 모델(glb) 올리기 ② 광고용 건물 고르기 → 이미지형이면 판마다 이미지, 텍스처형이면 텍스처 한 장 ③ 기본 광고 캐릭터 + 소품 고르기 → 소품 판에 이미지나 간판 글자 ④ 모델 제작 요청(등급 선택). 규격과 미리보기가 보여요.
- 노출 공간: 광고 게재에 동의한 공간 목록에서 분류·지금 접속자 수·하루 공간 클릭을 보고 자리를 직접 고르거나, 「추천으로」를 누르면 업종·분류·트래픽을 맞춘 공간 묶음을 추천해요.
- 과금 방식(CPM·CPA·노출 시간·공간 고정 계약)·예산·기간을 정하고 신청 → 검수 대기.
- 검수(음란·폭력·저작권 의심 등)를 통과하면 집행돼요. 진행 중인 캠페인은 멈추고 다시 켤 수 있고, 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