API Endpoint
POST https://app.specify.sh/api/adsAuthentication
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",
"adUnitId": "header-landscape-1"
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
walletAddresses | string[] | Yes | Array of Ethereum wallet addresses (1-50 addresses) |
imageFormat | string | Yes | Image format required for placement. Will match best ad with supported image format. See below for all formats. |
| adUnitId | string | No | Id used to identify individual ad placements / units. Useful for performance analytics. |
| localId | string | No | Random ID assigned for more efficient serving even after a user disconnects wallet (false by default). Supported only on browser clients. Recommended to not use directly with API. |
Image Formats
| Format | Aspect Ratio | Resolution | Use Case |
|---|---|---|---|
LANDSCAPE | 16:9 | 640x360 | Hero banners, featured placements |
LONG_BANNER | 8.09: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
0xfollowed by 40 hexadecimal characters - Case-insensitive
- Example:
0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510
Response Format
Returned Data (200)
| 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) / less than 400 characters | 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* |
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",
"adUnitId": "homepage-banner"
}'JavaScript (Fetch API)
async function getAd(walletAddress, imageFormat = 'LANDSCAPE', adUnitId = null) {
try {
const requestBody = {
walletAddresses: [walletAddress],
imageFormat: imageFormat
};
// Add adUnitId only if provided
if (adUnitId) {
requestBody.adUnitId = adUnitId;
}
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(requestBody)
});
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 examples
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);
});
// Usage with adUnitId
getAd('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 'LONG_BANNER', 'sidebar-ad')
.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', ad_unit_id=None):
url = 'https://app.specify.sh/api/ads'
headers = {
'Content-Type': 'application/json',
'x-api-key': api_key
}
data = {
'walletAddresses': [wallet_address],
'imageFormat': image_format
}
# Add adUnitId only if provided
if ad_unit_id:
data['adUnitId'] = ad_unit_id
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 examples
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}')
# Usage with adUnitId
try:
ad = get_ad('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 'SHORT_BANNER', ad_unit_id='header-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', $adUnitId = null) {
$url = 'https://app.specify.sh/api/ads';
$data = [
'walletAddresses' => [$walletAddress],
'imageFormat' => $imageFormat
];
// Add adUnitId only if provided
if ($adUnitId !== null) {
$data['adUnitId'] = $adUnitId;
}
$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 examples
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;
}
// Usage with adUnitId
try {
$ad = getAd('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', 'LANDSCAPE', 'spk_your_publisher_key_here', 'footer-banner');
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"`
AdUnitId string `json:"adUnitId,omitempty"` // omitempty excludes field if empty
}
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, adUnitId string) (*AdResponse, error) {
url := "https://app.specify.sh/api/ads"
requestBody := AdRequest{
WalletAddresses: []string{walletAddress},
ImageFormat: imageFormat,
AdUnitId: adUnitId, // Will be omitted from JSON if empty due to omitempty tag
}
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() {
// Usage without adUnitId
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")
}
// Usage with adUnitId
adWithUnit, err := getAd("0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510", "LANDSCAPE", "spk_your_publisher_key_here", "main-content")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
if adWithUnit != nil {
fmt.Printf("Ad headline: %s\n", adWithUnit.Headline)
fmt.Printf("Ad content: %s\n", adWithUnit.Content)
fmt.Printf("CTA: %s -> %s\n", adWithUnit.CTALabel, adWithUnit.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
Last updated on