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 withGET /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 toPOST /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:
null for a label the page does not state, and for an evidence field you do not have.
landingUrlis 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 aslandingKeySha256.inputDigestis the SHA-256 of what your reading read. The page, thelabelVersion, and theinputDigesttogether identify a card.labelVersionnames one way of reading pages. The name starts withestate-, followed by 1 to 57 characters ofa-z,0-9, dot, underscore, and hyphen. StefanBrain keeps every other name for its own readings. The first write under a name binds it totaxonomyVersion,model, andpromptVersion, and a name is never freed.evidenceis stored with the card and is not shown in benchmarks. When the industry is<sector>.other, put the market you would name inproposedIndustry.
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 withdryRun set to false. The response is 200 with one result for each item, in the order you sent them:
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 asitems.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 thetoolsscope.403 landing_page_label_writer_required: the account does not hold the grant.404 taxonomy_version_not_found: no list has thistaxonomyVersion.409 label_version_mismatch: thelabelVersionis bound to anothertaxonomyVersion,model, orpromptVersion. Send the reading under a newlabelVersion.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.

