메타모아 광고 관리

메타모아 3D 광고 · 3D ads

한눈에 보기

메타모아 3D 광고는 광고주의 3D 모델·광고용 건물·광고 캐릭터를 웹 메타버스 공간 안 광고 자리에 띄우고, 사람이 실제로 본 것과 한 일만 세어 광고비를 받고 그 몫을 공간 운영자에게 나누는 광고예요. 광고업·광고대행업으로 대기업부터 중소기업까지 받아요.

상품 4종과 과금

금액은 모두 공급가, 부가세 별도예요.

상품billing세는 것단가
CPM (viewable 노출)cpmviewable 노출 1회 = 모델이 화면 안에 일정 몫 이상, 정해진 시간 넘게 이어서 보임(탭이 보이는 상태)viewable 1,000회당 8,000원
CPA · 모델 클릭cpa광고 모델을 탭·클릭. 클릭한 뒤 브랜드 공간 입장까지 가면 입장 1건만 청구클릭 1회당 700원
CPA · 브랜드 공간 입장cpa광고를 눌러 브랜드 공간(brand_url)으로 들어감. 서버 리다이렉트(/ads/go)로 세고, 같은 사람·캠페인은 하루 한 번입장 1회당 1,000원
노출 시간timeviewable 노출 뒤 광고가 보인 누적 시간(입력 활동이 있을 때만, 한 사람·모델 하루 상한까지)viewable 1,000초당 1,000원
공간 고정 계약fixed기간제(최대 한 달). 계약한 광고 자리를 그 기간 동안 혼자 씀(자리 단위). 노출 수는 보장하지 않음아래 등급표

공간 고정 계약 등급

등급은 그 자리가 있는 공간의 최근 14일 평균 하루 공간 클릭으로 정해요. 가장 낮은 등급에 못 미치는 공간의 자리는 공간 고정 계약으로 팔지 않고 성과형으로만 받아요. 계약 기간은 주간 7일 · 월간 30일 중 하나예요.

등급하루 공간 클릭주간(자리 1개)월간(자리 1개)
S1,000 이상50,000원180,000원
A300~99915,000원50,000원
B100~2995,000원18,000원

집계 공식

날짜는 한국 시간 하루 단위(ad_daily). 금액은 원 단위 아래를 버린 공급가예요. 규칙에 걸린 이벤트는 세지 않고 이유별로 따로 세요(ad_excluded).

운영자 배분 비율: 매체 공급가의 60%

광고 자리(슬롯)

운영자가 자기 메타버스 안에 광고가 설 자리를 직접 정해요. 광고주는 자리 단위로 고르거나 추천을 받고, 공간 고정 계약도 자리 단위로 묶어요. 자리 목록은 공간 관리 화면에서 보고 켜고 끌 수 있어요.

칸형식뜻
name1~40자공간 안에서 겹치지 않는 이름
kindfloor · 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]
roam0~50 미터character 자리: 캐릭터가 걸어 다니는 반지름(0이면 서 있음)
industries업종 id 목록이 자리가 받는 업종. 비우면 모든 업종
enabledtrue · 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」 칸의 스크립트 한 줄을 공간 페이지 <head>나 <body> 끝에 넣어요. 광고 키(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).
<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가 세는 방법

봇·부정 검증 규칙

이벤트는 서버에서 아래 규칙을 차례로 거쳐요. 하나라도 걸리면 집계에서 빼고 그 규칙 이름으로 따로 세요. 규칙은 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)한 사람이 하루에 정해진 수를 넘게 본 노출은 빼고, 클릭 비율이 네트워크 평균의 정해진 배를 넘는 공간은 지급을 보류하고 살펴본다.켬

이벤트 규격

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칸뜻
imptoken, ms, area, inside, vis, focus, inputs, wdms: 이어서 보인 시간, area: 화면 넓이 대비 모델 넓이(0~1), inside: 모델 중 화면 안 몫(0~1), vis·focus: 탭 보임·창 포커스, inputs: 그동안 카메라·포인터 입력 수, wd: navigator.webdriver
timetoken, ms, vis, inputs, wd지난 time 뒤로 보인 시간(15,000ms까지)과 그 사이 입력 수
clicktoken, 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-bangjang1.8m모아 야영지 캐릭터 · KayKit(CC0) 바탕, 플로트 주식회사
야영지 창모아camp-changmoa1.8m모아 야영지 캐릭터 · KayKit(CC0) 바탕, 플로트 주식회사
야영지 링크블린camp-linkblin1.6m모아 야영지 캐릭터 · Quaternius(CC0) 바탕, 플로트 주식회사
빈센트vincent1.8m플로트 주식회사
냥줍고 고양이cat-short0.6mCat by Polyfork (https://polyfork.dev/asset/cat-base), CC BY 4.0, 플로트 주식회사가 고침
소품id이미지 비율·최소 크기
목걸이 팻말necklace1:1, 512×512px 이상
손 피켓picket4:3, 800×600px 이상
풍선balloon1:1, 512×512px 이상
깃발flag3:2, 600×400px 이상
쇼핑백bag3:4, 600×800px 이상

모델 제작 요청

광고주가 「모델 제작 요청」을 고르면 우리가 3D 모델을 만들어 캠페인에 붙인 뒤 검수해요. 만들 자료(제품 사진·로고·도면)는 [email protected]로 캠페인 번호와 함께 보내 주세요.

등급grade제작비이 매체비 이상 집행하면 무료
단순 제품·간판simple50,000원 (공급가, 부가세 별도)500,000원
정밀 제품precise100,000원 (공급가, 부가세 별도)1,000,000원
새 캐릭터character100,000원 (공급가, 부가세 별도)1,000,000원

광고주: 셀프 등록 순서

  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": "…"}.

검수

오류 코드

오류 본문은 { "error": "<코드>", "message": "<다음에 할 일 한 줄>" }이고, 칸 오류는 fields, 한도는 retry_after가 같이 와요.

HTTPerror뜻
400invalid칸 형식이 틀림. fields에 틀린 칸이 온다.
400bad_modelglb가 아님, glTF 2.0이 아님, 바깥 주소(uri)가 들어 있음. reason에 이유.
400bad_image이미지 형식·크기·비율이 규격과 다름. reason(format·bytes·too_large·too_small·aspect)과 spec이 온다.
400slot_unavailable고른 자리가 광고를 받지 않거나, 그 업종을 받지 않거나, 광고물 종류와 맞지 않음(캐릭터는 캐릭터 자리에만). slot_ids에 그 자리.
401unauthorized공간 키·공간 관리 로그인이 없거나 틀림.
401sign_in광고주: 메타모아 계정 로그인이 없거나 끝남.
403owner_required공간 키는 있지만 운영자 확인 전.
403owner_page_only재동의·광고 게재 동의를 공간 키로 시도함(공간 관리 화면에서만 된다).
403bad_ad_keySDK: space와 key(mm_adpub_)가 맞지 않음.
403terms_required그 공간이 바뀐 약관에 재동의하지 않음.
403ads_off그 공간이 광고 게재 동의를 끔.
403origin_mismatchSDK 요청이 그 공간 사이트(같은 도메인)에서 오지 않음.
403not_yours광고주: 남의 파일 id를 씀.
404no_slot그 이름·id의 광고 자리가 없음.
404not_found그 캠페인·파일이 없음.
409not_listed목록에 오르지 않은(승인 전·숨김) 공간.
409name_taken같은 이름의 자리가 이미 있음.
409slot_limit공간마다 자리 30개까지.
409slot_taken그 기간에 다른 공간 고정 계약이 잡힌 자리.
409tier_too_low하루 공간 클릭이 가장 낮은 등급에 못 미치는 공간의 자리를 공간 고정 계약으로 고름. slot_ids에 그 자리.
409not_for_sale공간 고정 계약 단가가 없음.
409under_minimum최소 집행 매체비보다 적음. min_spend_won, spend_won이 온다.
409over_inventoryCPM 예산이 고른 자리의 예상 viewable 노출 상한을 넘음. max_budget_won이 온다.
409rejected_file검수에서 막힌 파일을 캠페인에 씀.
409wrong_state그 상태에서 못 하는 일(검수 중 캠페인 멈추기 등).
409model_missing관리자: 모델 제작 요청 캠페인에 모델을 붙이기 전에 승인함.
413model_too_big모델 파일이 6MB를 넘음.
429too_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