Specify docs

API reference

Serve Specify ads through the HTTP API when you can't use the SDK or prefer raw requests.

Use the Specify HTTP API if you can't use the JavaScript SDK or prefer to manage requests yourself.

The SDK handles validation, the consent flag and the credentialed request for you. The HTTP API suits languages without a Specify SDK, and integrations that need control over the request lifecycle.

Endpoint

POST https://spfsrv.com/v1/ads

Authentication

Specify authenticates each request with the publisher key in its x-api-key header.

HeaderValueRequired
Content-Typeapplication/jsonYes
x-api-keyYour publisher key (starts with spk_)Yes

Publisher key format

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

Request

Body

{
  "walletAddresses": ["0x1111111111111111111111111111111111111111"],
  "imageFormat": "LANDSCAPE",
  "adUnitId": "header-landscape-1",
  "cookieConsent": false
}

Parameters

ParameterTypeRequiredDescription
walletAddressesstring[]Yes*Up to 50 EVM-compatible wallet addresses. For a user with multiple wallets, include all of them. See why. *Optional when the request carries Specify's identity cookie
imageFormatstringYesRequired image format for the placement. Specify matches the best ad available in that format. See formats
adUnitIdstringNoIdentifier for this specific placement. Used for per-placement analytics and A/B testing. Learn more
cookieConsentbooleanNoWhether the visitor consented to Specify's identity cookie. Defaults to false. Without it no cookie is read or set, and the request is matched on walletAddresses alone

Specify can set one cookie on spfsrv.com that recognises a returning visitor across every site in the network. It is what fills a placement for someone who has connected a wallet elsewhere but not on your page.

This only applies to browser requests, and only with cookieConsent: true. Send the request with credentials (credentials: 'include' in fetch) so the cookie can be set.

Wallet address validation

  • Must be valid Ethereum / EVM-compatible addresses
  • Must start with 0x followed by 40 hexadecimal characters
  • Case-insensitive
  • Send at most 50 addresses per request. The server removes duplicates.
  • Every address in a request must belong to the same person. This is not a bulk endpoint: asking for one ad on behalf of unrelated visitors mixes their onchain footprints and returns an ad that fits none of them. One request per visitor. See why.

Image formats

FormatRatioPixelsBest for
LANDSCAPE16:9640 × 360Hero banners, featured placements
LONG_BANNER8.09:11456 × 180Header and footer placements, leaderboards
SHORT_BANNER16:5640 × 200Inline content, sidebars, mobile banners
NO_IMAGEn/atext onlyDense lists, text feeds, places an image would not fit

For help choosing a format or building a native placement, see the Placements page.


Response

Success (200)

{
  "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"
}

Response fields

PropertyTypeDescription
campaignIdstringUnique identifier for the ad campaign
adIdstringUnique identifier for this specific ad
headlinestringAd headline text (plain text, no markdown)
contentstringAd body content. Supports simplified markdown. Max 400 characters
ctaUrlstringCall-to-action URL
ctaLabelstringCall-to-action button text (plain text, no markdown)
imageUrlstring | nullURL to the ad image (null for NO_IMAGE placements)
communityNamestringName of the advertising community (plain text, no markdown)
communityLogostringURL to the community logo
imageFormatstringThe format returned (LANDSCAPE, LONG_BANNER, SHORT_BANNER, or NO_IMAGE)
adUnitIdstringEchoes the adUnitId from the request, when one was sent

Specify returns the headline, content, CTA, and community branding as separate fields. Use them to build native placements as well as banners.

Content formatting

The content field supports a simplified markdown subset:

FormatSyntaxExample
Bold**text****Join today**
Italic*text**Italic*
Underline__text____Out now__
Bullet points* item* Feature 1\n* Feature 2
Line breaks\nFirst line\nSecond line

Error response

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

HTTP status codes

CodeMeaningNotes
200SuccessReturns ad content
204No ad availableExpected outcome, with no response body. Handle with fallback content
400Bad RequestInvalid input. Check the details array
401UnauthorizedInvalid or missing publisher key
503Serving unavailableSpecify could not resolve the request. Show fallback content

A 204 just means Specify had nothing relevant to serve to this user right now. Plan your UI around this case rather than treating it as failure. Anything other than a 200 with a body means render nothing.


Examples

curl -X POST https://spfsrv.com/v1/ads \
  -H "Content-Type: application/json" \
  -H "x-api-key: spk_your_publisher_key_here" \
  -d '{
    "walletAddresses": ["0x1111111111111111111111111111111111111111"],
    "imageFormat": "LANDSCAPE",
    "adUnitId": "homepage-banner"
  }'

On this page