| name | build-decisioning-signal-marketplace |
| description | Build an AdCP signal-marketplace OR signal-owned decisioning platform — a data provider serving audience signals to buyers. Use when the user wants the typed `DecisioningPlatform` shape; for fork-an-adapter starting points, see `build-signals-agent`. |
Build a Signals Decisioning Platform
You're building a signals data provider that fits one of two AdCP specialisms:
signal-marketplace — third-party data brokers serving curated signals (LiveRamp, Oracle Data Cloud, third-party DMPs)
signal-owned — first-party data providers serving their own signals (publisher first-party data, retailer customer-graph)
Both share the same SignalsPlatform interface. Pick the specialism that matches your relationship to the data; the implementation shape is identical.
When this skill applies
- User wants a signals platform on the typed
DecisioningPlatform surface
- Specialism:
signal-marketplace OR signal-owned
- SDK package:
@adcp/sdk
Wrong skill if:
- User wants to fork a worked adapter →
skills/build-signals-agent/
- User wants creative transforms →
skills/build-decisioning-creative-template/
- User wants to sell media inventory →
skills/build-seller-agent/
The whole shape (read this first)
A signals platform implements two methods:
getSignals(req, ctx) → Promise<GetSignalsResponse> — sync catalog discovery. Buyer sends filters; you return the matching signals. No async envelope.
activateSignal(req, ctx) → Promise<ActivateSignalSuccess> — sync ack with async lifecycle. Provision the signal onto destination platforms (Snap, Meta, TikTok, etc.); return immediately with deployments[] rows in current state (pending is valid). Each deployment's eventual activating / deployed / failed flows via publishStatusChange({ resource_type: 'signal', ... }).
Both throw AdcpError for buyer-fixable rejection.
Minimal worked example — DataMatrix marketplace
import {
AdcpError,
createAdcpServerFromPlatform,
publishStatusChange,
type DecisioningPlatform,
type SignalsPlatform,
type AccountStore,
} from '@adcp/sdk/server';
import type {
GetSignalsRequest,
GetSignalsResponse,
ActivateSignalRequest,
ActivateSignalSuccess,
AccountReference,
} from '@adcp/sdk/types';
import { serve } from '@adcp/sdk/server';
interface DataMatrixConfig {
minMatchRate: number;
}
interface DataMatrixMeta {
workspace_id: string;
}
class DataMatrixPlatform implements DecisioningPlatform<DataMatrixConfig, DataMatrixMeta> {
capabilities = {
specialisms: ['signal-marketplace'] as const,
creative_agents: [],
channels: [] as const,
pricingModels: ['cpm'] as ,
: { : } ,
};
: <> = {
: (: ) => {
id = ref ? ref. : ;
{
id,
: ,
: ,
: ,
: { : },
: { : },
};
},
};
: <> = {
: (: ): <> => {
{
: ,
: [
{
: { : , : , : },
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: [],
: [{ : , : , : , : }],
},
],
};
},
: (: ): <> => {
(!req..) {
(, {
: ,
: ,
: ,
});
}
: [] = req..(
d. ===
? {
: ,
: d.,
...(d. !== && { : d. }),
: ,
}
: {
: ,
: d.,
...(d. !== && { : d. }),
: ,
}
);
accountId = req. && req. ? req.. : ;
( dep deployments) {
( {
({
: accountId,
: ,
: req.,
:
dep. ===
? { : dep., : dep., : dep., : , : }
: { : dep., : dep., : dep., : , : },
});
}, ).?.();
}
{ deployments };
},
};
}
platform = ();
server = (platform, {
: ,
: ,
: { : , : },
});
( server, { : });
Sync ack with async lifecycle
activateSignal is always sync at the wire level — ActivateSignalResponse has no Submitted arm. For platforms with slow identity-graph matches (5-30 min) or destination provisioning (hours), the canonical pattern is:
- Return
ActivateSignalSuccess immediately with each deployment row in pending state
- Run the activation pipeline in background
- Emit
publishStatusChange({ resource_type: 'signal', ... }) for each deployment as it reaches activating / deployed / failed
Buyers subscribe via the resource-update channel to track activation progress.
Errors — throw new AdcpError(...)
Common codes for signals:
| Code | When |
|---|
'SIGNAL_NOT_FOUND' | unknown signal_agent_segment_id |
'POLICY_VIOLATION' | buyer lacks rights to activate this data |
'INVALID_REQUEST' | missing destinations, unrecognized destination shape, missing pricing_option_id when required |
'AUDIENCE_TOO_SMALL' | activated audience falls below match-rate floor |
'RATE_LIMITED' | upstream identity-graph throttled |
activateSignal: async req => {
if (!signalCatalog.has(req.signal_agent_segment_id)) {
throw new AdcpError('SIGNAL_NOT_FOUND', {
recovery: 'terminal',
message: `Unknown signal: ${req.signal_agent_segment_id}`,
field: 'signal_agent_segment_id',
});
}
};
Idempotency — the framework dedupes; you thread the key downstream
Same pattern as creative-template — see build-decisioning-creative-template/SKILL.md § Idempotency. Pass req.idempotency_key into your upstream identity-graph / destination-provisioning API so dedup is end-to-end.
Capabilities
capabilities = {
specialisms: ['signal-marketplace'] as const,
creative_agents: [],
channels: [] as const,
pricingModels: ['cpm'] as const,
config: {
} satisfies YourConfig,
};
Testing your platform
import { createAdcpServerFromPlatform } from '@adcp/sdk/server';
const platform = new DataMatrixPlatform();
const server = createAdcpServerFromPlatform(platform, {
name: 'dm-test',
version: '0.0.1',
validation: { requests: 'off', responses: 'off' },
});
const result = await server.dispatchTestRequest({
method: 'tools/call',
params: {
name: 'get_signals',
arguments: {
filters: { catalog_types: ['third_party'], industries: ['automotive'] },
account: { account_id: 'test_acc' },
},
},
});
console.log(result.structuredContent);
What NOT to do
❌ Don't try to make activateSignal HITL. The wire response has no Submitted arm. Sync ack + publishStatusChange is the correct pattern.
❌ Don't return error envelopes manually. Throw AdcpError; the framework projects to wire shape.
❌ Don't write as any / as never in adopter code. The wire types are typed; discriminators on SignalID (source: 'catalog' | 'agent') and Destination (type: 'platform' | ...) narrow without casts.
Reference: imports cheat sheet
import {
AdcpError,
AccountNotFoundError,
createAdcpServerFromPlatform,
publishStatusChange,
type DecisioningPlatform,
type AccountStore,
type Account,
type SignalsPlatform,
type RequestContext,
type ErrorCode,
type AdcpStructuredError,
} from '@adcp/sdk/server';
import type {
GetSignalsRequest,
GetSignalsResponse,
ActivateSignalRequest,
ActivateSignalSuccess,
AccountReference,
} from '@adcp/sdk/types';
import { serve } from '@adcp/sdk/server';