1const BUNDLE_API = "https://api.bundle.social/api/v1";2const headers = { "x-api-key": process.env.BUNDLE_SOCIAL_API_KEY!, "Content-Type": "application/json" };34// Onboarding: one team per customer, mapped in your own database.5export async function createTenant(customerId: string, name: string) {6 const res = await fetch(BUNDLE_API + "/team", { method: "POST", headers, body: JSON.stringify({ name }) });7 const team = await res.json();8 await db.tenants.insert({ customerId, bundleTeamId: team.id });9}1011// The API key reaches every team in the organization, so resolve teamId on the server12// from the signed-in user. Never pass through a teamId sent by the browser.13async function teamIdFor(session: { userId: string; customerId: string }) {14 const tenant = await db.tenants.findForMember(session.customerId, session.userId);15 if (!tenant) throw new Error("Forbidden");16 return tenant.bundleTeamId;17}1819export async function schedulePost(20 session: { userId: string; customerId: string },21 input: { title: string; text: string; postDate: string; uploadIds: string[] },22) {23 const teamId = await teamIdFor(session);24 const res = await fetch(BUNDLE_API + "/post", {25 method: "POST",26 headers,27 body: JSON.stringify({28 teamId,29 title: input.title,30 postDate: input.postDate,31 status: "SCHEDULED",32 socialAccountTypes: ["LINKEDIN", "INSTAGRAM"],33 data: {34 LINKEDIN: { text: input.text },35 INSTAGRAM: { type: "POST", text: input.text, uploadIds: input.uploadIds },36 },37 }),38 });39 return res.json();40}4142// Webhooks are organization-wide. Route each event to its tenant by data.teamId.43app.post("/webhooks/bundle", async (req, res) => {44 const { type, data } = req.body;45 const tenant = await db.tenants.findByBundleTeamId(data.teamId);46 if (tenant && type === "post.published") await notifyTenant(tenant, data.id, data.status);47 res.sendStatus(200);48});