Zernio
APIFor you
Zernio
APIFor you
Platforms

Meta Ads

Overview

Set up

Connect

Build

CampaignsAd setsCreativesCreative libraryPreviewsBoost a postLead Gen Forms

Target

TargetingCustom Audiences

Ad types

Creative testingConversion campaignsCatalog adsMessaging & call adsClick-to-WhatsApp adsReach & frequency

Measure

InsightsMeta PixelsConversions APIAd URL tracking tags

Operate

Duplicate & lifecycleAccount & ops readsAd LibraryAd comments

Reference

Enums, limits & error codes
Dashboard
OverviewPlatformsAPI ReferenceResources
Meta Ads

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/creatives lists the ad account's creatives.
  • POST /v1/ads/creatives makes 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.

TypeScript
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):

JSON
{
  "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 fieldSource
imageUrlA public URL, uploaded to the ad account server-side
imageHashA hash from POST /v1/ads/images or the image library list
carouselCards2 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.

TypeScript
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):

JSON
{
  "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:

  • callToAction defaults to LEARN_MORE.
  • description and urlTags are optional.
  • creativeFeatures follows the Advantage+ rules.
  • promotion attaches 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 type and nonnegative value
  • optional code, startDate and endDate
  • or null to 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:

JSON
{
  "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.

JSON
{ "accountId": "66b2e19d8c3f5a7e9d0b1c2d", "name": "Spring drop v2" }

Response (200):

JSON
{ "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.

TypeScript
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):

JSON
{
  "adAccountId": "act_1234567890",
  "image": {
    "hash": "2f1c8e0a9b7d6c5e4f3a2b1c0d9e8f7a",
    "url": "https://scontent.xx.fbcdn.net/v/t45.1600-4/..."
  }
}

Both halves are useful:

  • hash goes wherever Meta wants an image hash.
  • url is Meta-hosted and public, so it works directly as imageUrl on 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:

JSON
{
  "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:

JSON
{
  "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/create and 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.
Was this page helpful?

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.

On this page

List creativesCreate a creativePromotion readbackRename a creativeDelete a creativeAsset libraryImage libraryVideo libraryIf it failsRelated