Uçtan uca ajan içerik hattı: brif girer, hesaba özel gönderiler çıkar, tıklamalar geri gelir; Shager çağrıları sırasıyla
TypeScript ile Shager'da eksiksiz bir ajan hattı: brif, medya, hesaba özel açıklamalar, taslak, müşteri onayı, kademeli planlama, teslim kontrolü, tıklamalar.

Shager üzerinde uçtan uca bir ajan içerik hattı sekiz aşamadan oluşur: brifi okumak, medyayı herkese açık bir URL'de hazırlamak, hedef hesapları belirlemek, her hesap için ayrı bir açıklama yazmak, takip linkli bir taslak oluşturmak, onay almak, kademeli gönderimle planlamak, ardından teslimi doğrulayıp hesap bazında tıklamaları çekmek. Tekrara dayalı aşamalar ajanda, brif ve onay ise insanda. Shager'daki her adım tek bir REST çağrısı ya da aynı adı taşıyan tek bir MCP aracıdır.
Aşağıda zincirin tamamını, REST API'ye karşı yazılmış tek bir TypeScript programı olarak ve her aşamanın gerekçesiyle birlikte bulacaksınız. Hattı bir ajan istemcisinden yürütmeyi tercih ederseniz araç çağrıları birebir aynıdır.
Aşamalar neler, her birinin sahibi kim?
| Aşama | Sahibi | Shager çağrısı |
|---|---|---|
| 1. Brif | İnsan | Yok |
| 2. Medya | Ajan ya da kendi materyal hattınız | upload_media_from_url |
| 3. Hedefler | Ajan | list_channels, list_groups |
| 4. Açıklamalar | Ajan (sizin modeliniz) | Yok |
| 5. Taslak | Ajan | create_post |
| 6. Onay | Müşteri | request_approval |
| 7. Planlama | publish yetkili ajan ya da insan |
schedule_post |
| 8. Doğrulama ve ölçüm | Ajan | get_post, get_analytics |
Dil modeline ihtiyaç duyan tek aşama 4. aşama. Geri kalan her şey deterministik altyapı işidir ve modelin doğaçlamasına bırakılmak yerine kod olarak yazılmalıdır. Ajanla dağıtımda güvenilirlik adına çıkarılacak en büyük ders bu ayrımdır: yazmayı modele, orkestrasyonu koda bırakın.

1. aşama: brife ne girer?
Hattın üzerinde iş yapabileceği bir brifte sekiz alan bulunur. Daha azı olursa ajan tahmin yürütmeye başlar.
type Brief = {
workspace: string; // "Cedar Clinics"
group: string; // "All clinics"
message: string; // the facts, written by a human
mediaUrl: string; // public URL of the finished asset
media: { width: number; height: number; durationMs?: number };
link: string; // landing page to track
campaign: string; // "flu-shots-2026"
publishAt: string; // "2026-10-27T08:00:00-05:00"
};
publishAt bilerek bir saat farkı (offset) taşır. Shager, schedule_at için saat farkı içeren ISO 8601 biçimini zorunlu tutar; saat farkını brife yazmak da "sabah 8"in hangi saat dilimini kastettiğini kimsenin hatırlamak zorunda kalmaması demektir.
Kurulum: tüm çağrılar için tek yardımcı
const API = "https://app.shager.ai/api/v1";
class ShagerError extends Error {
constructor(public status: number, public code: string, message: string) {
super(`${status} ${code}: ${message}`);
}
}
async function shager<T>(method: string, path: string, body?: unknown): Promise<T> {
for (let attempt = 0; attempt < 3; attempt++) {
const res = await fetch(`${API}${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.SHAGER_API_KEY}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (res.ok) return json.data as T;
if (res.status === 429) {
await new Promise((r) => setTimeout(r, 60_000));
continue;
}
throw new ShagerError(res.status, json.error.code, json.error.message);
}
throw new Error("Rate limited three times in a row");
}
Yalnızca 429 yeniden denenir. 403 ya da 422 bir bilgidir; yeniden denemek aynı reddi tekrarlamaktan öte bir işe yaramaz. Sınır anahtar başına dakikada 120 istektir ve bu hat ona yaklaşmaz bile, çünkü tek bir create_post tüm hesapları kapsar.
2. aşama: medya
Shager medyayı bir URL'den içe aktarır; yani materyali kim ürettiyse (bir editör, bir render işi, bir üretim aracı) onu herkesin erişebileceği bir yere bırakması yeterlidir.
const workspaces = await shager<Array<{ id: string; name: string }>>("GET", "/workspaces");
const ws = workspaces.find((w) => w.name === brief.workspace);
if (!ws) throw new Error(`No workspace named ${brief.workspace}`);
const media = await shager<{ id: string; kind: string }>("POST", `/workspaces/${ws.id}/media`, {
url: brief.mediaUrl,
width: brief.media.width,
height: brief.media.height,
duration_ms: brief.media.durationMs,
});
Genişlik, yükseklik ve süre dikey video için önemlidir. Bunlar verildiğinde Shager, Reels, Shorts ve hikayeleri planlamadan önce her ağın kurallarına göre doğrular. Böylece bir format için fazla uzun olan bir klip, gönderim anında tek bir ağda patlamak yerine şimdi, okunabilir bir gerekçeyle hata verir.

3. aşama: hedefleri belirleyin
type Channel = { id: string; network: string; name: string; status: string; formats: string[] };
const groups = await shager<Array<{ id: string; name: string; channel_ids: string[] }>>(
"GET", `/workspaces/${ws.id}/groups`,
);
const group = groups.find((g) => g.name === brief.group);
if (!group) throw new Error(`No group named ${brief.group}`);
const channels = (await shager<Channel[]>("GET", `/workspaces/${ws.id}/channels`))
.filter((c) => group.channel_ids.includes(c.id) && c.status === "ACTIVE");
Burada ACTIVE ile filtrelemek, bağlantısı kopmuş hesapları en baştan raporlamanızı sağlar. Token'ı süresi dolmuş bir sayfa, gönderi başarısız olduktan sonra fark edilecek bir şey değil, müşteriyle konuşulacak bir konudur.
4. aşama: her hesaba bir açıklama
Modelinizin hakkını verdiği yer burası. Aşağıdaki fonksiyon bilerek boş bırakılmış bir taslaktır; ekibinizin kullandığı model ve SDK'yı buraya bağlayın.
async function writeCaptions(brief: Brief, channels: Channel[]): Promise<Map<string, string>> {
// Prompt your model with: the brief's facts, each channel's name and network,
// and these rules: keep facts identical, vary the opening and structure,
// use the local detail in the channel name, no raw URLs, network-appropriate length.
// Return channel_id -> caption, and validate that every channel got one.
throw new Error("connect your model here");
}
const captions = await writeCaptions(brief, channels);
Bu isteme her seferinde iki kural girmeli. Birincisi, her yerde aynı bilgiler; çünkü sayfadan sayfaya kayan bir fiyat ya da tarih gerçek bir hatadır. İkincisi, çıplak link yok; çünkü link ayrıca eklenir ve her hesap kendi takip edilen sürümünü alır. Varyasyonun nerede işe yarayıp nerede zarar verdiğini birçok hesapta marka tutarlılığı rehberimizde anlatıyoruz.
5. aşama: taslağı oluşturun
type PostView = {
id: string;
status: string;
destinations: Array<{ channel_id: string; account: string; network: string; format: string; state: string; url: string | null; error: string | null }>;
};
const draft = await shager<PostView>("POST", `/workspaces/${ws.id}/posts`, {
label: `${brief.campaign} ${brief.publishAt.slice(0, 10)}`,
caption: brief.message,
channel_ids: channels.map((c) => c.id),
media_ids: [media.id],
per_channel: channels.map((c) => ({ channel_id: c.id, caption: captions.get(c.id)! })),
cta_url: brief.link,
campaign: brief.campaign,
first_comment: `Details: ${brief.link}`,
stagger_seconds: [30, 90],
});
cta_url, her hesaba hesabı tanımlayan utm_content parametresiyle kendi kısa linkini verir. first_comment, her gönderi yayına girer girmez paylaşılır. stagger_seconds, aynı ağdaki hesapları rastgele 30 ila 90 saniye arayla dağıtır; bu aynı zamanda varsayılan değerdir. Sonuç bir DRAFT'tır.
6. aşama: onay
const approval = await shager<{ approval_url: string; emailed: boolean }>(
"POST", `/workspaces/${ws.id}/posts/${draft.id}/approval`,
{ reviewer_name: "Priya Shah", reviewer_email: "priya@cedarclinics.example", send_email: true },
);
console.log("Approval link:", approval.approval_url);
Onaylayan kişi linki açar ve giriş yapmadan onaylar. Hat burada durur ve daha sonra kaldığı yerden devam eder; onay milisaniyeler değil, saatler sürer. draft.id değerini saklayın ve get_post ile APPROVED durumunu kontrol eden ikinci bir çalıştırmada onu tekrar ele alın. Bu kontrol noktasının neden yayından sonra değil de taslaktan sonra durduğunu merak ediyorsanız, gerekçeyi ajanslar için müşteri onay akışları yazısında bulabilirsiniz.
7. aşama: planlama
const post = await shager<PostView>("GET", `/workspaces/${ws.id}/posts/${draft.id}`);
if (post.status !== "APPROVED") {
console.log(`Not approved yet: ${post.status}`);
} else {
try {
await shager<PostView>("POST", `/workspaces/${ws.id}/posts/${draft.id}/schedule`, {
schedule_at: brief.publishAt,
});
} catch (e) {
if (e instanceof ShagerError && e.code === "rejected") {
console.error("A network rule failed:", e.message); // fix input, do not loop
} else throw e;
}
}
Bunun için, https://app.shager.ai/dashboard/agents adresinde bir insanın verdiği publish yetkisine sahip bir anahtar gerekir. Shager bu noktada her hedefi kendi ağının kurallarına göre doğrular; dolayısıyla 422 rejected yanıtı, kuyruğa herhangi bir şey girmeden önce gelir.
8. aşama: teslimi doğrulayın ve ölçün
Yayın saatinden sonra her hedefi kontrol edin. Teslim durumları PENDING, QUEUED, PROCESSING, PUBLISHED, FAILED ve SKIPPED'dir.
const after = await shager<PostView>("GET", `/workspaces/${ws.id}/posts/${draft.id}`);
for (const d of after.destinations) {
if (d.state === "FAILED" || d.state === "SKIPPED") console.warn(d.account, d.state, d.error);
}
const stats = await shager<{ total_clicks: number; by_account: Array<{ account: string; network: string; clicks: number }> }>(
"GET", `/workspaces/${ws.id}/analytics?from=2026-10-27T00:00:00-05:00&to=2026-11-03T00:00:00-05:00`,
);
console.table(stats.by_account);
Hesap bazındaki tıklamalar, Shager'ın işinin bittiği ve müşterinin işinin başladığı yerdir. Bunları CRM'de utm_content üzerinden potansiyel müşterilerle eşleştirmeyi hesap bazında potansiyel müşteri ilişkilendirmesi yazısında anlatıyoruz.
SSS
Bunu bir ajan istemcisi mi çalıştırmalı, yoksa bir betik mi?
İkisi de olur. Sabit aşamalar ve zamanlayıcıyla çalışan her şey için betik daha iyidir. Brif belirsizse ve açıklamalar üzerinde etkileşimli olarak ilerlemek istiyorsanız Claude Code gibi bir ajan istemcisi daha uygundur. Birçok ekip 4. aşama için ajanı, geri kalanı için kodu kullanır.
Brif birden fazla müşterinin hesaplarını hedefliyorsa ne olur?
Hattı her çalışma alanı için bir kez çalıştırın. Çalışma alanları müşterileri bilerek birbirinden ayırır: hesapları, onayları, linkleri ve analizleri asla karışmaz; müşteri de raporunu okurken tam olarak bunu bekler.
Alanların tam tanımlarını nerede bulabilirim?
Her işlemi, girdiyi ve yetkiyi listeleyen OpenAPI belgesinde. Ajanlar sayfası da aynı bilgileri ajanlar ve onları geliştirenler için daha kısa biçimde sunar.
Sonraki adım
Hattı önce tek bir test hesabıyla ve hiç planlamayacağınız bir taslakla çalıştırın. Taslağı Shager'da inceleyin, hesap bazındaki linkleri kontrol edin, ardından tüm gruba genişletin. Bu sağlam çalıştığında aşamaları haftalık bir rutine sarın; böylece hat birinin hafızasına göre değil, bir takvime göre çalışır.


