Publishing

Table of Content

Table of Content

Table of Content

SDK Reference

This JavaScript/TypeScript SDK allows publishers to serve personalized ads to users based on their Ethereum or EVM-compatible wallet addresses.

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'
});

// Serve an ad to a wallet address
try {
  const ad = await specify.serve('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 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

Parameter

Type

Description

config

SpecifyInitConfig

Configuration object

config.publisherKey

string

Your publisher API key (must start with spk_ and be 34 characters long)

Example

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

SDK Reference

serve()

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

serve(
  addressOrAddresses: Address | Address[], 
  imageFormat: ImageFormat
): Promise<SpecifyAd | null

Parameters

Parameter

Type

Description

addressOrAddresses

Address | Address[]

Single wallet address or array of wallet addresses

imageFormat

ImageFormat

Desired image format for the ad content

Returns

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

Examples

Single wallet address:

import { ImageFormat } from 'specify-publisher-sdk';

const ad = await specify.serve(
  '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510',
  ImageFormat.LANDSCAPE
);

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

const ad = await specify.serve([
  '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510',
  '0x8ba1f109551bD432803012645Hac136c0532925a',
  '0x1234567890123456789012345678901234567890'
], ImageFormat.LONG_BANNER);

Image Format Options

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

Format

Aspect Ratio

Resolution

Description

Use Case

LANDSCAPE

16:9

630x390

Wide horizontal format

Hero banners, featured placements

LONG_BANNER

8:1

728x90

Extended horizontal banner

Header/footer placements, leaderboards

SHORT_BANNER

16:5

320x100

Compact horizontal banner

Inline content, sidebars, mobile banners

Return Type

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

Property

Type

Description

walletAddress

string

The wallet address that matched for this ad

campaignId

string

Unique identifier for the ad campaign

adId

string

Unique identifier for this specific ad

headline

string (no markdown)

Ad headline text

content

string (markdown)

Ad content with Markdown formatting (see Content Formatting section)

ctaUrl

string

Call-to-action URL

ctaLabel

string (no markdown)

Call-to-action button text

imageUrl

string

URL to the ad image

communityName

string (no markdown)

Name of the advertising community

communityLogo

string

URL to the community logo

imageFormat

string

The requested image format (LANDSCAPE, LONG_BANNER, or SHORT_BANNER)

Content Formatting

The content field in ad responses uses simplified Markdown formatting:

Format

Syntax

Example

Bold text

**text**

**Join today** for exclusive benefits

  • Bullet points

* item

* Feature 1\n* Feature 2\n* Feature 3

Line breaks

\n

First 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-publisher-sdk';

try {
  const ad = await specify.serve(walletAddress, ImageFormat.LANDSCAPE);
  
  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-publisher-sdk';

const specify = new Specify({
  publisherKey: process.env.REACT_APP_SPECIFY_PUBLISHER_KEY!
});

interface AdComponentProps {
  walletAddress: string;
  imageFormat?: ImageFormat;
}

const AdComponent: React.FC<AdComponentProps> = ({ 
  walletAddress, 
  imageFormat = ImageFormat.LANDSCAPE 
}) => {
  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);
        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]);

  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-publisher-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' } = req.query;
    
    const ad = await specify.serve(walletAddress, format as ImageFormat);
    
    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');
});




©

2025

The Internet Community Company. All rights reserved.

©

2025

The Internet Community Company. All rights reserved.

©

2025

The Internet Community Company. All rights reserved.