Skip to main content
The Landing Page Labels API stores one label card for each landing page a Meta ad sends people to. A card holds six labels: industry, product form, target gender, target age, price band, and page kind. StefanBrain reads the cards it has made live to group cross-account Meta benchmarks by market. Use this API only if StefanBrain has given your account the landing_page_label_writer grant. Every other account receives 403 landing_page_label_writer_required from both endpoints. Use a full-access stefan_sk_... key or one scoped to include tools, or any stefan_oat_... OAuth token (OAuth tokens are not scoped).

Read the label lists

Each label is a value of a fixed list. Read the lists of one list version with GET /api/developers/v1/meta/label-values/{taxonomyVersion}:
dimension is one of sector, industry, product_form, target_gender, target_age, price_band, and page_kind. You never send a sector: it is the first part of the industry id. Build your labeling prompt from these rows so that it names the same values the write accepts. This GET does not consume request limits or a billing budget. It returns 404 taxonomy_version_not_found when no list has that version.

Check a batch with a dry run

Send 1 to 200 items to POST /api/developers/v1/meta/landing-page-labels. The body is UTF-8 JSON of at most 32 MB. Set dryRun to true to run every check and store nothing:
Every field is required. Send null for a label the page does not state, and for an evidence field you do not have.
  • landingUrl is the ad’s own raw URL. Do not normalize it. StefanBrain computes the page’s landing key from it and returns the key’s SHA-256 as landingKeySha256.
  • inputDigest is the SHA-256 of what your reading read. The page, the labelVersion, and the inputDigest together identify a card.
  • labelVersion names one way of reading pages. The name starts with estate-, followed by 1 to 57 characters of a-z, 0-9, dot, underscore, and hyphen. StefanBrain keeps every other name for its own readings. The first write under a name binds it to taxonomyVersion, model, and promptVersion, and a name is never freed.
  • evidence is stored with the card and is not shown in benchmarks. When the industry is <sector>.other, put the market you would name in proposedIndustry.
Two raw URLs with the same landingKeySha256 are the same page. Use the dry run to group your raw URLs by key, then send one item for each page.

Write labels

Send the same body with dryRun set to false. The response is 200 with one result for each item, in the order you sent them:
A refused item does not stop the other items of the request. The write is rate-limited but does not consume the API wallet or plan pool. A stored card is not shown in benchmarks until StefanBrain makes its label version live. A card that you write under a label version that is already live is shown at once.

Handle each item result

A stored card is never edited. A correction is a new card.

Refusal reasons

Handle request errors

These errors refuse the whole request. Nothing is written.
  • 400 invalid_json: the body is not valid UTF-8, or it is not a JSON object.
  • 400 invalid_landing_page_label_request: the body breaks the request shape. The message starts with the path of the field, such as items.3.inputDigest.
  • 400 invalid_taxonomy_version: the list version in the path is not a positive whole number.
  • 403 api_key_scope_forbidden: use a credential with the tools scope.
  • 403 landing_page_label_writer_required: the account does not hold the grant.
  • 404 taxonomy_version_not_found: no list has this taxonomyVersion.
  • 409 label_version_mismatch: the labelVersion is bound to another taxonomyVersion, model, or promptVersion. Send the reading under a new labelVersion.
  • 413 request_too_large: the body is over 32 MB. Send fewer items in each request.
  • 429: wait for the returned reset time before you send another write.
The request shape also refuses text that holds a NUL character or half of a surrogate pair, because that text cannot be stored as sent. A body in another encoding, such as Latin-1, is refused for the same reason.