Publishing

Table of Content

Table of Content

Table of Content

SDK Reference

Specify Publisher SDK

The Specify Publisher SDK provides access to targeted advertising content based on end user wallet addresses. 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'
});

API 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:

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

Choosing the Right Format

import { ImageFormat } from '@specify-sh/sdk';

// For hero sections or main content areas
const heroAd = await specify.serve(walletAddress, ImageFormat.LANDSCAPE);

// For website headers or newsletter banners  
const headerAd = await specify.serve(walletAddress, ImageFormat.LONG_BANNER);

// For sidebar or inline placements
const sidebarAd = await specify.serve(walletAddress, ImageFormat.SHORT_BANNER);

Type Definitions

Address

type Address = `0x${string}`;

ImageFormat

enum ImageFormat {
  LANDSCAPE = "LANDSCAPE",
  LONG_BANNER = "LONG_BANNER",
  SHORT_BANNER = "SHORT_BANNER"
}

SpecifyAd

interface SpecifyAd {
  walletAddress: string;
  campaignId: string;
  adId: string;
  headline: string;
  content: string;
  ctaUrl: string;
  ctaLabel: string;
  imageUrl: string;
  communityName: string;
  communityLogo: string;
  imageFormat: keyof typeof ImageFormat;
}

SpecifyInitConfig

interface SpecifyInitConfig {
  publisherKey: string;
}

APIErrorResponse

interface APIErrorResponse {
  error: string;
  details?: Array<{
    field: string;
    message: string;
  }>;
}

Error Handling

The SDK throws specific error types for different scenarios:

AuthenticationError

Thrown when the publisher key is invalid or authentication fails.

try {
  const specify = new Specify({ publisherKey: 'invalid_key' });
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Authentication failed:', error.message);
  }
}

ValidationError

Thrown when input validation fails (invalid wallet addresses, too many addresses, etc.).

try {
  await specify.serve('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', ImageFormat.LANDSCAPE);
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation error:', error.message);
    console.error('Details:', error.details); // Additional error details if available
  }
}

NotFoundError

Thrown when no ad content is found for the specified wallet address(es).

try {
  const ad = await specify.serve('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', ImageFormat.LANDSCAPE);
} catch (error) {
  if (error instanceof NotFoundError) {
    console.log('No ad available for this wallet');
  }
}

APIError

Generic error for API-related issues (network errors, server errors, etc.).

try {
  const ad = await specify.serve('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', ImageFormat.LANDSCAPE);
} catch (error) {
  if (error instanceof APIError) {
    console.error('API error:', error.message);
    console.error('Status code:', error.status);
  }
}

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.