Publishing

Table of Content

Table of Content

Table of Content

API Reference

Specify Raw API Documentation

If you can't use the SDK or prefer to make direct HTTP requests, you can interact with the Specify API directly.

API Endpoint

POST https://app.specify.sh/api/ads

Authentication

Headers

Header

Value

Required

Content-Type

application/json

Yes

x-api-key

Your publisher key (starts with spk_)

Yes

Publisher Key Format

  • Must start with spk_

  • Must be exactly 34 characters long

  • Example: spk_1234567890abcdef1234567890abcdef

Request Format

Request Body

{
  "walletAddresses": ["0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510"],
  "imageFormat": "LANDSCAPE"
}

Parameters

Parameter

Type

Required

Description

walletAddresses

string[]

Yes

Array of Ethereum wallet addresses (1-50 addresses)

imageFormat

string

Yes

Image format: LANDSCAPE, LONG_BANNER, or SHORT_BANNER

Image Formats

Format

Aspect Ratio

Resolution

Use Case

LANDSCAPE

16:9

630x390

Hero banners, featured placements

LONG_BANNER

8:1

728x90

Header/footer placements, leaderboards

SHORT_BANNER

16:5

320x100

Inline content, sidebars, mobile banners

Wallet Address Validation

  • Must be valid Ethereum/EVM-compatible addresses

  • Must start with 0x followed by 40 hexadecimal characters

  • Case-insensitive

  • Example: 0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510

Response Format

Success Response (200)

{
  "walletAddress": "0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510",
  "campaignId": "campaign_123",
  "adId": "ad_456",
  "headline": "Join the Future of DeFi",
  "content": "Experience next-generation decentralized finance with our platform.",
  "ctaUrl": "https://example.com/signup",
  "ctaLabel": "Get Started",
  "imageUrl": "https://cdn.specify.sh/ads/image_123.png",
  "communityName": "DeFi Protocol",
  "communityLogo": "https://cdn.specify.sh/logos/community_123.png",
  "imageFormat": "LANDSCAPE"
}

Error Response (4xx/5xx)

{
  "error": "Error message describing what went wrong",
  "details": [
    {
      "field": "walletAddresses",
      "message": "Invalid wallet address format"
    }
  ]
}

HTTP Status Codes

Code

Description

200

Success - Returns ad content

400

Bad Request - Invalid input data

401

Unauthorized - Invalid API key

404

Not Found - No ad available for the wallet address(es)

500

Internal Server Error

Usage Examples

cURL

curl -X POST https://app.specify.sh/api/ads \
  -H "Content-Type: application/json" \
  -H "x-api-key: spk_your_publisher_key_here" \
  -d '{
    "walletAddresses": ["0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510"],
    "imageFormat": "LANDSCAPE"
  }'

JavaScript (Fetch API)

async function getAd(walletAddress, imageFormat = 'LANDSCAPE') {
  try {
    const response = await fetch('https://app.specify.sh/api/ads', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'spk_your_publisher_key_here'
      },
      body: JSON.stringify({
        walletAddresses: [walletAddress],
        imageFormat: imageFormat
      })
    });

    if (response.status === 404) {
      console.log('No ad found for this wallet');
      return null;
    }

    if (response.status === 401) {
      throw new Error('Invalid API key');
    }

    if (response.status === 400) {
      const errorData = await response.json();
      throw new Error(`Validation error: ${errorData.error}`);
    }

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const adData = await response.json();
    return adData;
  } catch (error) {
    console.error('Error fetching ad:', error);
    throw error;
  }
}

// Usage
getAd('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 'LONG_BANNER')
  .then(ad => {
    if (ad) {
      console.log('Ad data:', ad);
      console.log('Headline:', ad.headline);
      console.log('CTA:', ad.ctaLabel, '->', ad.ctaUrl);
    } else {
      console.log('No ad available');
    }
  })
  .catch(error => {
    console.error('Failed to get ad:', error);
  });

Python (requests)

import requests
import json

def get_ad(wallet_address, image_format='LANDSCAPE', api_key='spk_your_publisher_key_here'):
    url = 'https://app.specify.sh/api/ads'
    headers = {
        'Content-Type': 'application/json',
        'x-api-key': api_key
    }
    data = {
        'walletAddresses': [wallet_address],
        'imageFormat': image_format
    }
    
    try:
        response = requests.post(url, headers=headers, json=data)
        
        if response.status_code == 404:
            print('No ad found for this wallet')
            return None
            
        if response.status_code == 401:
            raise Exception('Invalid API key')
            
        if response.status_code == 400:
            error_data = response.json()
            raise Exception(f"Validation error: {error_data.get('error', 'Invalid request')}")
            
        response.raise_for_status()  # Raises an HTTPError for bad responses
        
        return response.json()
        
    except requests.RequestException as e:
        print(f'Error fetching ad: {e}')
        raise

# Usage
try:
    ad = get_ad('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 'SHORT_BANNER')
    if ad:
        print('Ad headline:', ad['headline'])
        print('Ad content:', ad['content'])
        print('CTA:', ad['ctaLabel'], '->', ad['ctaUrl'])
    else:
        print('No ad available')
except Exception as e:
    print(f'Failed to get ad: {e}')

PHP

<?php
function getAd($walletAddress, $imageFormat = 'LANDSCAPE', $apiKey = 'spk_your_publisher_key_here') {
    $url = 'https://app.specify.sh/api/ads';
    $data = [
        'walletAddresses' => [$walletAddress],
        'imageFormat' => $imageFormat
    ];
    
    $options = [
        'http' => [
            'header' => [
                'Content-Type: application/json',
                'x-api-key: ' . $apiKey
            ],
            'method' => 'POST',
            'content' => json_encode($data)
        ]
    ];
    
    $context = stream_context_create($options);
    $response = file_get_contents($url, false, $context);
    
    if ($response === FALSE) {
        throw new Exception('Failed to fetch ad');
    }
    
    // Get HTTP response code
    $httpCode = intval(substr($http_response_header[0], 9, 3));
    
    switch ($httpCode) {
        case 200:
            return json_decode($response, true);
        case 404:
            echo 'No ad found for this wallet';
            return null;
        case 401:
            throw new Exception('Invalid API key');
        case 400:
            $errorData = json_decode($response, true);
            throw new Exception('Validation error: ' . ($errorData['error'] ?? 'Invalid request'));
        default:
            throw new Exception('HTTP error! status: ' . $httpCode);
    }
}

// Usage
try {
    $ad = getAd('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 'LANDSCAPE');
    if ($ad) {
        echo 'Ad headline: ' . $ad['headline'] . PHP_EOL;
        echo 'Ad content: ' . $ad['content'] . PHP_EOL;
        echo 'CTA: ' . $ad['ctaLabel'] . ' -> ' . $ad['ctaUrl'] . PHP_EOL;
    } else {
        echo 'No ad available' . PHP_EOL;
    }
} catch (Exception $e) {
    echo 'Failed to get ad: ' . $e->getMessage() . PHP_EOL;
}

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type AdRequest struct {
    WalletAddresses []string `json:"walletAddresses"`
    ImageFormat     string   `json:"imageFormat"`
}

type AdResponse struct {
    WalletAddress string `json:"walletAddress"`
    CampaignID    string `json:"campaignId"`
    AdID          string `json:"adId"`
    Headline      string `json:"headline"`
    Content       string `json:"content"`
    CTAUrl        string `json:"ctaUrl"`
    CTALabel      string `json:"ctaLabel"`
    ImageUrl      string `json:"imageUrl"`
    CommunityName string `json:"communityName"`
    CommunityLogo string `json:"communityLogo"`
    ImageFormat   string `json:"imageFormat"`
}

func getAd(walletAddress, imageFormat, apiKey string) (*AdResponse, error) {
    url := "https://app.specify.sh/api/ads"
    
    requestBody := AdRequest{
        WalletAddresses: []string{walletAddress},
        ImageFormat:     imageFormat,
    }
    
    jsonData, err := json.Marshal(requestBody)
    if err != nil {
        return nil, err
    }
    
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
    if err != nil {
        return nil, err
    }
    
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("x-api-key", apiKey)
    
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    
    switch resp.StatusCode {
    case 200:
        var ad AdResponse
        if err := json.Unmarshal(body, &ad); err != nil {
            return nil, err
        }
        return &ad, nil
    case 404:
        return nil, nil // No ad found
    case 401:
        return nil, fmt.Errorf("invalid API key")
    case 400:
        return nil, fmt.Errorf("validation error: %s", string(body))
    default:
        return nil, fmt.Errorf("HTTP error! status: %d", resp.StatusCode)
    }
}

func main() {
    ad, err := getAd("0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510", "LANDSCAPE", "spk_your_publisher_key_here")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    
    if ad != nil {
        fmt.Printf("Ad headline: %s\n", ad.Headline)
        fmt.Printf("Ad content: %s\n", ad.Content)
        fmt.Printf("CTA: %s -> %s\n", ad.CTALabel, ad.CTAUrl)
    } else {
        fmt.Println("No ad available"

Multiple Wallet Addresses

You can send multiple wallet addresses in a single request (maximum 50 addresses):

const response = await fetch('https://app.specify.sh/api/ads', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'spk_your_publisher_key_here'
  },
  body: JSON.stringify({
    walletAddresses: [
      '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510',
      '0x8ba1f109551bD432803012645Hac136c0532925a',
      '0x1234567890123456789012345678901234567890'
    ],
    imageFormat: 'LONG_BANNER'
  })
});

The API will return a single ad that best matches the provided wallet addresses. To be used for one user with multiple wallets

Rate Limits

  • Maximum 50 wallet addresses per request

  • Duplicate addresses are automatically removed

  • Contact support if you need higher rate limits

Error Handling Best Practices

Always handle different HTTP status codes appropriately:

async function handleApiCall(walletAddress, imageFormat) {
  try {
    const response = await fetch('https://app.specify.sh/api/ads', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your-api-key'
      },
      body: JSON.stringify({
        walletAddresses: [walletAddress],
        imageFormat: imageFormat
      })
    });

    switch (response.status) {
      case 200:
        const ad = await response.json();
        return ad;
        
      case 404:
        console.log('No ad found, showing fallback content');
        return null;
        
      case 401:
        console.error('Invalid API key - check your credentials');
        throw new Error('Authentication failed');
        
      case 400:
        const errorData = await response.json();
        console.error('Validation error:', errorData.error);
        if (errorData.details) {
          errorData.details.forEach(detail => {
            console.error(`Field ${detail.field}: ${detail.message}`);
          });
        }
        throw new Error('Invalid request data');
        
      default:
        console.error('API error:', response.status);
        throw new Error(`HTTP error: ${response.status}`);
    }
  } catch (error) {
    console.error('Network or parsing error:', error);
    throw error;
  }
}

©

2025

The Internet Community Company. All rights reserved.

©

2025

The Internet Community Company. All rights reserved.

©

2025

The Internet Community Company. All rights reserved.