Skip to Content
PublishingSDK Reference - v0.4.2

Installation

npm install @specify-sh/sdk

Quick Start

import Specify, { ImageFormat } from '@specify-sh/sdk'; // Initialize the SDK const specify = new Specify({ publisherKey: 'spk_your_publisher_key_here', // Enable for more efficient client side serves cacheMostRecentAddress: true }); // Serve an ad to a wallet address try { const ad = await specify.serve('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', { imageFormat: ImageFormat.LANDSCAPE }); if (ad) { console.log('Ad content:', ad); console.log('Headline:', ad.headline); console.log('Content:', ad.content); console.log('Image URL:', ad.imageUrl); console.log('CTA:', ad.ctaLabel, '->', ad.ctaUrl); } else { console.log('No ad found for this wallet'); } } catch (error) { console.error('Error serving ad:', error); }

Configuration

Constructor

Creates a new Specify client instance.

const specify = new Specify(config: SpecifyInitConfig);

Parameters

ParameterTypeDescription
configSpecifyInitConfigConfiguration object
config.publisherKeystringYour publisher API key (must start with spk_ and be 34 characters long)
config.cacheMostRecentAddressbooleanAllows for more efficient serving even after a user disconnects wallet (false by default & only supported on the browser client)

Example

const specify = new Specify({ publisherKey: 'spk_1234567890abcdef1234567890abcdef', cacheMostRecentAddress: true });

SDK Reference

serve()

Serves targeted advertising content to specified wallet address(es).

serve( addressOrAddresses: Address | Address[] | undefined, options: ServeOptions ): Promise<SpecifyAd | null >

Parameters

ParameterTypeDescriptionRequired
addressOrAddressesAddress | Address[] | undefinedSingle wallet address or array of wallet addresses. Undefined or empty only works with cacheMostRecentAddress = trueYes
optionsServeOptionsConfiguration options for serving the adYes
options.imageFormatImageFormatImage format required for placement. Will match best ad with supported image format. See below for all formats.Yes
options.adUnitIdstringId used to identify individual ad placements / units. Useful for performance analytics.No

ServeOptions Interface

interface ServeOptions { imageFormat: ImageFormat; adUnitId?: string; }

Returns

Promise<SpecifyAd | null> - Ad content object or null if no ad is found

Examples

Single wallet address:

import { ImageFormat } from '@specify-sh/sdk'; const ad = await specify.serve( '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', { imageFormat: ImageFormat.LANDSCAPE } );

With adUnitId:

const ad = await specify.serve( '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', { imageFormat: ImageFormat.LANDSCAPE, adUnitId: 'homepage-hero-banner' } );

Multiple wallet addresses - used when you have multiple addresses for one user:

const ad = await specify.serve([ '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', '0x8ba1f109551bD432803012645Hac136c0532925a', '0x1234567890123456789012345678901234567890' ], { imageFormat: ImageFormat.LONG_BANNER, adUnitId: 'multi-wallet-banner' });

Undefined wallet addresses - With cacheMostRecentAddress we can serve ads even after the user disconnects their wallet:

const ad = await specify.serve(null, { imageFormat: ImageFormat.LONG_BANNER, adUnitId: 'multi-wallet-banner' }); // Or const ad = await specify.serve([], { imageFormat: ImageFormat.LONG_BANNER, adUnitId: 'multi-wallet-banner' });

Image Format Options

The Specify SDK supports three different ad image formats to match your layout needs:

FormatAspect RatioResolutionDescriptionUse Case
LANDSCAPE16:9640x360Wide horizontal formatHero banners, featured placements
LONG_BANNER8.09:11456x180Extended horizontal bannerHeader/footer placements, leaderboards
SHORT_BANNER16:5640x200Compact horizontal bannerInline content, sidebars, mobile banners

Return Type

When the serve() method successfully finds an ad, it returns a SpecifyAd object with the following properties:

PropertyTypeDescription
walletAddressstringThe wallet address that matched for this ad
campaignIdstringUnique identifier for the ad campaign
adIdstringUnique identifier for this specific ad
headlinestring (no markdown)Ad headline text
contentstring (markdown)less than 400 characters
ctaUrlstringCall-to-action URL
ctaLabelstring (no markdown)Call-to-action button text
imageUrlstringURL to the ad image
communityNamestring (no markdown)Name of the advertising community
communityLogostringURL to the community logo
imageFormatstringThe requested image format (LANDSCAPE, LONG_BANNER, or SHORT_BANNER)

Content Formatting

The content field in ad responses uses simplified Markdown formatting:

FormatSyntaxExample
Bold text**text**Join today for exclusive benefits
Bullet points* item* Feature 1\n* Feature 2\n* Feature 3
Line breaks\nFirst line\nSecond line
Underlined Text__text__Out now
Italic Text*text*Italic

Validation Rules

Publisher Key

  • Must start with spk_
  • Must be exactly 34 characters long
  • Example: spk_1234567890abcdef1234567890abcdef

Wallet Addresses

  • Must be valid Ethereum/EVM-compatible addresses
  • Must start with 0x followed by 40 hexadecimal characters
  • Case-insensitive
  • Example: 0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510

Address Limits

  • Minimum: 1 wallet address required
  • Maximum: 50 wallet addresses per request
  • Duplicate addresses are automatically removed

Best Practices

Error Handling

Always wrap SDK calls in try-catch blocks and handle specific error types:

import { AuthenticationError, ValidationError, NotFoundError, APIError, ImageFormat } from '@specify-sh/sdk'; try { const ad = await specify.serve(walletAddress, { imageFormat: ImageFormat.LANDSCAPE, adUnitId: 'main-content-ad' }); if (ad) { // Display the ad displayAd(ad); } else { // Handle no ad case showFallbackContent(); } } catch (error) { if (error instanceof ValidationError) { console.error('Invalid input:', error.message); } else if (error instanceof NotFoundError) { console.log('No ad available'); showFallbackContent(); } else if (error instanceof AuthenticationError) { console.error('Authentication failed. Check your publisher key.'); } else if (error instanceof APIError) { console.error('API error:', error.message); showErrorMessage(); } else { console.error('Unexpected error:', error); } }

Examples

React Integration

import React, { useState, useEffect } from 'react'; import Specify, { SpecifyAd, ValidationError, NotFoundError, ImageFormat } from '@specify-sh/sdk'; const specify = new Specify({ publisherKey: process.env.REACT_APP_SPECIFY_PUBLISHER_KEY! }); interface AdComponentProps { walletAddress: string; imageFormat?: ImageFormat; adUnitId?: string; } const AdComponent: React.FC<AdComponentProps> = ({ walletAddress, imageFormat = ImageFormat.LANDSCAPE, adUnitId }) => { const [ad, setAd] = useState<SpecifyAd | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { const fetchAd = async () => { try { setLoading(true); setError(null); const adData = await specify.serve(walletAddress, { imageFormat, adUnitId }); setAd(adData); } catch (err) { if (err instanceof ValidationError) { setError('Invalid wallet address'); } else if (err instanceof NotFoundError) { setAd(null); // No ad available } else { setError('Failed to load ad'); } } finally { setLoading(false); } }; if (walletAddress) { fetchAd(); } }, [walletAddress, imageFormat, adUnitId]); if (loading) return <div>Loading ad...</div>; if (error) return <div>Error: {error}</div>; if (!ad) return <div>No ad available</div>; return ( <div className="ad-container"> <div className="ad-header"> <img src={ad.communityLogo} alt={ad.communityName} className="community-logo" /> <span className="community-name">{ad.communityName}</span> </div> <h3 className="ad-headline">{ad.headline}</h3> <img src={ad.imageUrl} alt={ad.headline} className="ad-image" /> <p className="ad-content">{ad.content}</p> <a href={ad.ctaUrl} className="ad-cta" target="_blank" rel="noopener noreferrer"> {ad.ctaLabel} </a> </div> ); }; export default AdComponent;

Node.js Server Integration

import express from 'express'; import Specify, { ValidationError, NotFoundError, ImageFormat } from '@specify-sh/sdk'; const app = express(); const specify = new Specify({ publisherKey: process.env.SPECIFY_PUBLISHER_KEY! }); app.get('/api/ads/:walletAddress', async (req, res) => { try { const { walletAddress } = req.params; const { format = 'LANDSCAPE', adUnitId } = req.query; const ad = await specify.serve(walletAddress, { imageFormat: format as ImageFormat, adUnitId: adUnitId as string | undefined }); if (ad) { res.json({ success: true, ad: { ...ad, // You can transform or add additional fields here timestamp: new Date().toISOString() } }); } else { res.status(404).json({ success: false, message: 'No ad found' }); } } catch (error) { if (error instanceof ValidationError) { res.status(400).json({ success: false, message: error.message, details: error.details }); } else if (error instanceof NotFoundError) { res.status(404).json({ success: false, message: 'No ad found' }); } else { console.error('Server error:', error); res.status(500).json({ success: false, message: 'Internal server error' }); } } }); app.listen(3000, () => { console.log('Server running on port 3000'); });
Last updated on