Creative library
Manage Meta ad creatives as standalone objects with GET and POST /v1/ads/creatives, and upload, list and delete the ad account's image and video assets.
A creative on Meta outlives the ad that uses it.
GET /v1/ads/creativeslists the ad account's creatives.POST /v1/ads/creativesmakes one without an ad.- The image and video endpoints fill the ad account's asset library.
Any creative id from here is reusable as existingCreativeId on POST /v1/ads/create or POST /v1/ads/preview.
You need accountId and adAccountId. Every endpoint here is Meta only.
List creatives
Call GET /v1/ads/creatives with accountId and adAccountId.
Rows are Meta's /adcreatives rows verbatim: id, name, status, object type, thumbnail, and the story or asset feed spec.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: creatives } = await zernio.adcreatives.listAdCreatives({
query: { accountId: '66b2e19d8c3f5a7e9d0b1c2d', adAccountId: 'act_1234567890', limit: 50 }
});
const nextCursor = creatives.paging.after;Response (200):
{
"adAccountId": "act_1234567890",
"data": [
{
"id": "120250000000000005",
"name": "Spring sale - US feed",
"status": "ACTIVE",
"object_type": "SHARE",
"thumbnail_url": "https://scontent.xx.fbcdn.net/...",
"object_story_spec": { "page_id": "811889972008357", "link_data": { "link": "https://example.com/spring" } }
}
],
"paging": { "after": "QVFIUm..." }
}fields overrides the default projection. limit and after page.
GET /v1/ads/creatives/{creativeId}?accountId=... returns one creative the same way.
created_time on one creative. The creative node rejects created_time even though the list edge returns it. Zernio's default projection drops that field on the node read.
A fields value you send is forwarded to Meta unchanged. fields=id,name,created_time on the node still comes back as Meta's #100.
Create a creative
Call POST /v1/ads/creatives with headline, body, linkUrl and exactly one of:
| Media field | Source |
|---|---|
imageUrl | A public URL, uploaded to the ad account server-side |
imageHash | A hash from POST /v1/ads/images or the image library list |
carouselCards | 2 to 10 cards, the same shape as carousel ads, except every card needs linkUrl as well as imageUrl |
The Page, and the linked Instagram account when there is one, resolve from accountId, so there is no Page id in the body.
const { data: creative } = await zernio.adcreatives.createAdCreative({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: 'act_1234567890',
headline: 'Spring drop is live',
body: 'Three colourways, limited run.',
linkUrl: 'https://example.com/spring',
imageUrl: 'https://cdn.example.com/spring.jpg',
creativeFeatures: { enhance_cta: 'OPT_IN' }
}
});
const creativeId = creative.creativeId;Response (201):
{
"adAccountId": "act_1234567890",
"creativeId": "120250000000000007"
}imageUrl is the shortest path: Meta fetches the file and uploads it into the ad account for you.
Send imageHash instead when the image is already in the library (image library).
The other fields:
callToActiondefaults toLEARN_MORE.descriptionandurlTagsare optional.creativeFeaturesfollows the Advantage+ rules.promotionattaches an explicit offer (Promotion offers), with a live readback only when supplied.
The create call takes no name. Set one afterwards with rename.
Creative names are unique per ad account, deleted creatives included, so a name you deleted last month still collides. Suffix generated names with a timestamp or a counter.
Promotion readback
The optional promotion input uses the same offer fields and validation:
- required
typeand nonnegativevalue - optional
code,startDateandendDate - or
nullto omit the offer on a new creative
It is separate from creativeFeatures.auto_promotion_tag.
Only when you supply promotion does POST /v1/ads/creatives read the creative back from Meta. The response adds top-level promotion and promotionStatus, alongside creativeId:
{
"adAccountId": "act_1234567890",
"creativeId": "120250000000000007",
"promotion": null,
"promotionStatus": "not_returned"
}promotionStatus values:
applied: Meta returned promotion metadata.not_returned: the read succeeded without that metadata.unavailable: the read failed.
A successful create alone does not prove the offer was applied or will display.
Attaching this creative with existingCreativeId preserves its settings. New promotion settings do not modify the reused creative.
Rename a creative
Call PUT /v1/ads/creatives/{creativeId} with accountId and name.
The name is the only mutable field. To change copy, media or the button, create a new creative and swap it onto the ad with PUT /v1/ads/{adId}.
The old creative keeps its own reporting history.
{ "accountId": "66b2e19d8c3f5a7e9d0b1c2d", "name": "Spring drop v2" }Response (200):
{ "creativeId": "120250000000000007", "name": "Spring drop v2", "message": "Creative renamed" }A GET issued right after the rename can return the old name for a few seconds. Re-read after a moment rather than treating it as a failure.
Delete a creative
DELETE /v1/ads/creatives/{creativeId}?accountId=... succeeds only when no ad references the creative. Otherwise Meta's 400 surfaces verbatim.
Delete or repoint the ads first.
Asset library
Image library
GET /v1/ads/images lists the ad account's /adimages rows: hash, a Meta-hosted url, name, dimensions and status.
Any hash is reusable wherever Meta accepts an image hash, imageHash above included.
POST /v1/ads/images uploads an image from base64, for creatives whose media has no public URL. The limit is 30 MB decoded.
const { data: upload } = await zernio.adcreatives.uploadAdImage({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: 'act_1234567890',
imageBase64: 'data:image/jpeg;base64,/9j/4AAQSkZJRg...',
filename: 'promo.jpg'
}
});
const imageHash = upload.image.hash;Response (201):
{
"adAccountId": "act_1234567890",
"image": {
"hash": "2f1c8e0a9b7d6c5e4f3a2b1c0d9e8f7a",
"url": "https://scontent.xx.fbcdn.net/v/t45.1600-4/..."
}
}Both halves are useful:
hashgoes wherever Meta wants an image hash.urlis Meta-hosted and public, so it works directly asimageUrlon the create endpoints. It is the shortest path from bytes in memory to a running ad.
With a hash in hand, the create above becomes:
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"adAccountId": "act_1234567890",
"headline": "Spring drop is live",
"body": "Three colourways, limited run.",
"linkUrl": "https://example.com/spring",
"imageHash": "2f1c8e0a9b7d6c5e4f3a2b1c0d9e8f7a"
}Meta dedupes uploads by content, so uploading the same bytes twice returns the same hash.
Video library
Video has the same pair of calls plus a delete.
POST /v1/ads/videos takes one of:
- a public
videoUrl, downloaded server-side videoBase64, capped around 4.5 MB by the request body limit
It blocks until Meta reports the video ready. Chunked upload and transcoding can stretch that to minutes.
It returns video.id, plus Meta's poster URL when it has produced one. The id is reusable as video: { id } on create and inside a preview creative spec.
GET /v1/ads/videos lists the account's videos, including ones uploaded in Ads Manager or another tool. It is the only way to reach those.
A row is usable once its status.video_status reads ready.
Each row carries source, a signed playable MP4 URL that expires. Play it to pick a video, never store it.
DELETE /v1/ads/videos/{videoId}?accountId=...&adAccountId=... removes one from the library. It returns { "success": true } from Meta verbatim.
Both query parameters are required, and the ad account must be the one that owns the video.
If it fails
A 422 on create means no Facebook Page could act as the creative's story actor:
{
"error": "No Facebook Page found to act as the story actor",
"type": "invalid_request_error"
}Pass an accountId on the same profile as the ad account that is either a connected Facebook account or an Instagram account connected with Facebook Login (before you start).
Related
- Creatives: the creative shapes on
POST /v1/ads/createand swapping a creative on a live ad. - Previews: render a library creative by id.
- Account reads: the ad labels Ads Manager groups creatives with.
- Creative library and Upload an ad image: every field.
- Upload an ad video, Ad video library and Delete an ad video: the video half.
Creatives
Build Meta ad creatives on POST /v1/ads/create: video, per-placement assets, carousels, multi-language ads and Advantage+ enhancements. Swap the creative on a live ad and read its media.
Previews
Render a Meta ad, or a creative that does not exist yet, as Meta renders it, with GET /v1/ads/{adId}/preview and POST /v1/ads/preview.