fix: fall back to Facebook-only when ad account can't use the page's Instagram

Meta rejects the whole ad creative with error 200/1815199 when the ad
account isn't authorized (in Business Manager) to use the Instagram
account linked to the chosen page. Picking a different page in the new
selector doesn't help if that page's IG is also unshared. Now, when the
creative fails for that specific reason, we retry without
instagram_actor_id so the ad still publishes on Facebook, and the modal
explains why Instagram was dropped and how to enable it.

createFullCampaign gets the same fallback for free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcio Bevervanso 2026-06-08 14:21:18 -03:00
parent dcaf7959ac
commit a88e96adf5
2 changed files with 49 additions and 18 deletions

View file

@ -80,6 +80,7 @@ export function AddAdModal({
const [publishing, setPublishing] = useState(false); const [publishing, setPublishing] = useState(false);
const [error, setError] = useState<FriendlyError | string | null>(null); const [error, setError] = useState<FriendlyError | string | null>(null);
const [done, setDone] = useState(false); const [done, setDone] = useState(false);
const [instagramDropped, setInstagramDropped] = useState(false);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
@ -271,6 +272,7 @@ export function AddAdModal({
return; return;
} }
setInstagramDropped(Boolean(data.data?.instagram_dropped));
setDone(true); setDone(true);
onCreated(); onCreated();
} catch (err) { } catch (err) {
@ -301,6 +303,13 @@ export function AddAdModal({
<p className="text-xs" style={{ color: 'var(--muted-fg)' }}> <p className="text-xs" style={{ color: 'var(--muted-fg)' }}>
Ele foi criado como Pausado ative-o no Ads Manager (ou na tabela) quando estiver pronto. Ele foi criado como Pausado ative-o no Ads Manager (ou na tabela) quando estiver pronto.
</p> </p>
{instagramDropped && (
<div className="text-[11px] mt-1 px-3 py-2 rounded-lg text-left" style={{ background: 'rgba(234,179,8,0.08)', border: '1px solid rgba(234,179,8,0.2)', color: '#fcd34d' }}>
Publicamos <strong> no Facebook</strong>: a conta de anúncios ainda não tem permissão pra usar
{selectedInstagramAccount ? <> a conta <strong>@{selectedInstagramAccount.username}</strong></> : ' a conta do Instagram'} desta página.
Para ativar o Instagram, vincule a conta no Business Manager (Configurações do Negócio Contas do Instagram Atribuir conta de anúncios) e republique.
</div>
)}
<button onClick={onClose} className="btn-primary mt-2">Fechar</button> <button onClick={onClose} className="btn-primary mt-2">Fechar</button>
</div> </div>
) : ( ) : (

View file

@ -608,26 +608,30 @@ export async function addAdToAdSet(client: Client, params: AddAdToAdSetParams) {
// the most broadly compatible across placements. // the most broadly compatible across placements.
const primaryImageHash = imageHashes[0]; const primaryImageHash = imageHashes[0];
let creative; // Monta o criativo com (ou sem) o Instagram. Quando a conta de anúncios não
if (params.is_whatsapp) { // tem permissão pra usar a conta de IG vinculada à página, a Meta recusa o
creative = await createWhatsappAdCreative(client, { // criativo inteiro (erro 200 / subcode 1815199). Nesse caso refazemos o
// criativo só com o Facebook, em vez de barrar a publicação.
const buildCreative = (instagramActorId?: string) => {
if (params.is_whatsapp) {
return createWhatsappAdCreative(client, {
name: creativeName,
page_id: params.page_id,
instagram_actor_id: instagramActorId,
image_hash: primaryImageHash,
message: params.primary_text,
headline: params.headline,
description: params.description,
link: params.link,
whatsapp_link: params.whatsapp_link,
whatsapp_phone_number: params.whatsapp_phone_number,
ad_account_id: params.ad_account_id,
});
}
return createAdCreative(client, {
name: creativeName, name: creativeName,
page_id: params.page_id, page_id: params.page_id,
instagram_actor_id: params.instagram_actor_id, instagram_actor_id: instagramActorId,
image_hash: primaryImageHash,
message: params.primary_text,
headline: params.headline,
description: params.description,
link: params.link,
whatsapp_link: params.whatsapp_link,
whatsapp_phone_number: params.whatsapp_phone_number,
ad_account_id: params.ad_account_id,
});
} else {
creative = await createAdCreative(client, {
name: creativeName,
page_id: params.page_id,
instagram_actor_id: params.instagram_actor_id,
image_hash: primaryImageHash, image_hash: primaryImageHash,
link: params.link, link: params.link,
message: params.primary_text, message: params.primary_text,
@ -636,6 +640,23 @@ export async function addAdToAdSet(client: Client, params: AddAdToAdSetParams) {
call_to_action_type: params.cta, call_to_action_type: params.cta,
ad_account_id: params.ad_account_id, ad_account_id: params.ad_account_id,
}); });
};
// 1815199: "A conta de anúncios não tem acesso a esta conta do Instagram".
const isInstagramPermissionError = (err: unknown) =>
err instanceof Error && /1815199/.test(err.message);
let creative;
let instagramDropped = false;
try {
creative = await buildCreative(params.instagram_actor_id);
} catch (err) {
if (params.instagram_actor_id && isInstagramPermissionError(err)) {
creative = await buildCreative(undefined);
instagramDropped = true;
} else {
throw err;
}
} }
const creativeId = (creative as any)?.id; const creativeId = (creative as any)?.id;
@ -659,6 +680,7 @@ export async function addAdToAdSet(client: Client, params: AddAdToAdSetParams) {
adset_id: params.adset_id, adset_id: params.adset_id,
creative_id: creativeId, creative_id: creativeId,
ad_id: adId, ad_id: adId,
instagram_dropped: instagramDropped,
}; };
} }