Developer documentation

Reading a product link

When someone pastes a product URL into Sandy Giftto create a wishlist post, we fetch that page once and read its public metadata to pre-fill the post’s name, image and price.

Overview

We read two sources, in order: schema.org Product JSON-LD first, then Open Graph tags for anything JSON-LD did not supply. The result is merged field-by-field, so a page with JSON-LD name and only an og:image still fills in completely.

Requests come from the end user’s own device at the moment they paste a link — not from a Sandy Gift server — and are debounced to one request per link. Every field is editable by the user before the post is created.

Product JSON-LD

What we look for first, and what we recommend. It is the same structured data Google Shopping and rich results consume, so most storefronts already publish it. It is also the only source that reliably carries a price.

HTML
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": "Ninja CREAMi Scoop & Swirl Ice Cream Maker",
  "image": "https://cdn.example.com/products/creami-large.jpg",
  "offers": {
    "@type": "Offer",
    "price": "4299.00",
    "priceCurrency": "ZAR",
    "availability": "https://schema.org/InStock"
  }
}
</script>
FieldFillsNotes
nameTitlePreferred over og:title — it is the bare product name, with no SEO suffix.
imageImageA URL string, an ImageObject (contentUrl preferred, then url), or an array of either. First usable HTTPS entry wins.
offers.pricePriceNumber or string. Falls back to offers.lowPrice, so AggregateOffer works.
offers.priceCurrencyCurrencyISO 4217. Ignored when it is not a supported currency.

We scan every application/ld+json block and search each one for a Product. It may sit at the top level, inside an array, or nested under @graph, mainEntity or itemListElement, up to six levels deep. A @type array containing "Product" matches too. If one block contains malformed JSON we skip it and keep checking the rest, so a single broken script will not cost you the page.

Open Graph

Used for any field JSON-LD did not supply. Nearly every site has og:title and og:image; the price tags are much rarer, which is why JSON-LD is worth adding if you only do one thing.

HTML
<meta property="og:title"               content="Ninja CREAMi Scoop &amp; Swirl Ice Cream Maker" />
<meta property="og:image"               content="https://cdn.example.com/products/creami-large.jpg" />
<meta property="product:price:amount"   content="4299.00" />
<meta property="product:price:currency" content="ZAR" />
FieldFillsNotes
og:titleTitleA trailing “ | Site Name” suffix is trimmed.
og:imageImageog:image:secure_url also accepted. Must be HTTPS.
product:price:amountPriceog:price:amount also accepted. Often absent — this is why JSON-LD is preferred.
product:price:currencyCurrencyog:price:currency also accepted.

Tags match on either the property or name attribute, in any attribute order, and the first occurrence of a key wins. HTML entities in content are decoded.

Price formats

Prices are accepted as numbers or strings, with or without symbols and separators. The last separator is read as a decimal point only when one or two digits follow it; otherwise separators are treated as thousands markers.

PublishedRead as
569569
"4299.00"4299
"R 4 299,00"4299
"1 299"1299
"0" · "-5" · "POA"discarded

Supported currencies

A priceCurrencyoutside this list is ignored, and the price is applied using the user’s own default currency instead.

ZARUSDEURGBPNGNKESGHSBWPMZNZMWNADAUDCADNZDJPYCNYINRBRLCHFSEKAEDSARKRWMXNSGD

Requirements & limits

  • Metadata is present in the initial HTML response. We do not execute JavaScript, so a head populated after hydration is invisible to us.
  • The URL responds 200 with an HTML content type. Redirects are followed.
  • Metadata sits inside <head> — only the first 512KB of the response is parsed.
  • The page responds within 8 seconds.
  • Image URLs are HTTPS. Protocol-relative (//cdn…) is upgraded; plain http:// is dropped.
  • The image URL serves a real image content type — jpeg, png, webp, gif or avif. The file extension is ignored, so an extensionless or oddly-named URL is fine when the header is right.
  • A price of zero, a negative price, or a non-numeric price is discarded.
  • An unsupported currency is discarded, and the price is kept against the user’s own default currency.

Testing your pages

The failure we see most often is metadata that only exists after hydration. To check a page the way we read it, request it without a browser and confirm the metadata is in the response body.

Terminal
# Is your metadata there without JavaScript?
curl -sL -A "facebookexternalhit/1.1" \
  "https://example.com/your-product-page" \
  | grep -E 'application/ld\+json|og:title|og:image|product:price'

No output means the metadata is not server-rendered, and the post will not pre-fill. Note that many storefronts render metadata only for recognised link-preview crawlers, so a plain curl with no user agent can return an empty shell even when sharing to social apps works — test with a preview crawler agent, as above.

How images are used

We do not hotlink. When the user posts, the image is copied once into Sandy Gift’s own storage and served from there.

So your CDN serves one request per post rather than one per feed view, and the post keeps working after you rotate the URL or delist the product. Both the image and the price are a snapshot taken at post time — they do not track your page afterwards.

What we do not do

  • We do not execute JavaScript or run a headless browser.
  • We do not crawl. Only the exact URL a user pasted is requested — no links followed, no sitemaps, no discovery.
  • We do not authenticate, submit forms, or add anything to a cart.
  • We do not retain your page HTML. Only the extracted name, price and image are used.

Questions

There is no public Sandy Gift API yet. If your product pages are not reading correctly, or you want to hear when one is available, get in touch.

support@sandygift.com