Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 3 additions & 16 deletions content/guides/billing.mdx
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
---
title: Billing
description: Preview the upcoming CALL-E billing model. These rates are not yet in effect.
description: CALL-E pricing, billing periods, and call cost examples.
---

import { ClockIcon } from "zudoku/icons";
import { BillingComparison } from "../../src/components/BillingComparison.js";

<section className="billing-highlight" aria-label="Billing preview">
<p className="billing-highlight__title">
<ClockIcon aria-hidden="true" />
<span>Billing preview — coming soon</span>
</p>
<div className="billing-highlight__body">
This billing model is under development and is not yet in effect. The rates
below are a preview; your current billing remains unchanged.
</div>
</section>

<section className="billing-offer" aria-label="Goal pricing offer">
<p className="billing-offer__badge"><ClockIcon aria-hidden="true" /><span>Coming soon</span></p>
<div className="billing-offer__hero">
<p className="billing-offer__headline"><span>Choose Goal.</span><span>Pay half.</span></p>
<div className="billing-offer__discount">
Expand All @@ -28,7 +15,7 @@ import { BillingComparison } from "../../src/components/BillingComparison.js";
</div>
</div>
<BillingComparison>
<p>30-second domestic outbound call · US / CA · USD · Preview</p>
<p>30-second domestic outbound call · US / CA · USD</p>
<p>One-shot-call: Model Fee $0.0296 × 3 = $0.0888; Carrier Fee $0.0400/min × 1 min = $0.0400; total $0.1288.</p>
<p>Goal: Model Fee $0.0148 × 3 = $0.0444; Carrier Fee $0.0200/min × 1 min = $0.0200; total $0.0644 (50% less).</p>
<p>You save $0.0644 on this 30-second call with Goal.</p>
Expand All @@ -49,7 +36,7 @@ Choose Goal for batch and large-scale calling. Keep One-shot-call for developmen

**Total cost = Carrier Fee + Model Fee**

Under this model, you will pay Carrier Fee for telephone lines and Model Fee for combined model usage.
You pay Carrier Fee for telephone lines and Model Fee for combined model usage.
All prices are in USD.

<div className="billing-note">
Expand Down
2 changes: 1 addition & 1 deletion content/guides/calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Create one phone task and receive a structured result. Start with the

The **one-shot Calls API** (`POST /v2/calls`) is designed for quick integration, development, and testing. Each request creates one phone task, with no published Goal needed.

For batch and large-scale calling, we recommend [Goal Runs](/goal-runs), which will cost **50% less** under the [upcoming billing model](/billing). This pricing is not yet in effect.
For batch and large-scale calling, we recommend [Goal Runs](/goal-runs), which cost **50% less** than One-shot-call. See [Billing](/billing) for rates and examples.

</aside>

Expand Down
4 changes: 2 additions & 2 deletions src/components/BillingComparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function BillingComparison({ children }: { children: ReactNode }) {
dismiss();
}}
>
<div className="billing-cost-tooltip__context">{price.name} · {fee.label} · Preview</div>
<div className="billing-cost-tooltip__context">{price.name} · {fee.label}</div>
<div className="billing-cost-tooltip__rate">
{money(price[fee.rateKey])}<span> / {fee.unit}</span>
</div>
Expand Down Expand Up @@ -122,7 +122,7 @@ export function BillingComparison({ children }: { children: ReactNode }) {
<span><i className="billing-comparison__model" />Model Fee · / 10s</span>
<span><i className="billing-comparison__carrier" />Carrier Fee · / min</span>
</div>
<p className="billing-comparison__hint"><InfoIcon aria-hidden="true" /> Hover or tap a segment for its rate · Preview</p>
<p className="billing-comparison__hint"><InfoIcon aria-hidden="true" /> Hover or tap a segment for its rate</p>
{/* Preserve the equivalent breakdown in Markdown and LLM exports. */}
<div hidden>{children}</div>
</figure>
Expand Down
41 changes: 0 additions & 41 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
--code-scrollbar: #475569;
}

.billing-highlight,
.calling-mode-guidance {
margin-block: 1.5rem 2rem;
padding: 1.25rem 1.5rem;
Expand All @@ -52,7 +51,6 @@
color: var(--foreground);
}

.billing-highlight > p,
.calling-mode-guidance > p {
margin-block: 0.75rem 0;
}
Expand All @@ -61,27 +59,6 @@
margin-top: 0;
}

.billing-highlight__body > p {
margin-block: 0.75rem 0;
}

.billing-highlight > .billing-highlight__title {
display: flex;
align-items: flex-start;
gap: 0.625rem;
margin-top: 0;
color: var(--call-signal);
font-size: 1.125rem;
font-weight: 700;
}

.billing-highlight__title > svg {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
margin-top: 0.25rem;
}

.billing-offer {
margin-block: 1.5rem 2rem;
padding: 1.75rem;
Expand All @@ -93,24 +70,6 @@
color: #f8fbff;
}

.billing-offer > .billing-offer__badge {
display: inline-flex;
align-items: center;
gap: 0.375rem;
margin: 0 0 1.125rem;
padding: 0.3125rem 0.625rem;
border: 1px solid #476381;
border-radius: 999px;
color: #d9e9fa;
font-size: 0.75rem;
font-weight: 500;
}

.billing-offer__badge > svg {
width: 0.875rem;
height: 0.875rem;
}

.billing-offer__hero {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
Expand Down
32 changes: 14 additions & 18 deletions tests/docs-site.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,9 @@ test("documents aggregate billing and preserves pre-connection policy", async ({
await page.goto("/billing");
const article = page.locator('[data-pagefind-body="true"]');
await expect(page.getByRole("heading", { level: 1, name: "Billing" })).toBeVisible();
const billingPreview = article.getByRole("region", { name: "Billing preview", exact: true });
await expect(billingPreview).toBeVisible();
await expect(billingPreview).toContainText("Billing preview — coming soon");
await expect(billingPreview).toContainText("under development and is not yet in effect");
await expect(billingPreview).toContainText("your current billing remains unchanged");
await expect(billingPreview.locator('svg[aria-hidden="true"]')).toBeVisible();
await expect(article.getByRole("region", { name: "Billing preview", exact: true })).toHaveCount(0);
await expect(article).not.toContainText(/preview|coming soon|upcoming|not yet in effect|under development/i);
await expect(page.locator('meta[name="description"]')).toHaveAttribute("content", "CALL-E pricing, billing periods, and call cost examples.");
await expect(article).toContainText("Total cost = Carrier Fee + Model Fee");
await expect(article).not.toContainText("Total cost = Carrier Fee + Model Fee + Task Success Fee");
const billingNote = article.locator(".billing-note").filter({ hasText: "pre-connection" });
Expand All @@ -229,12 +226,11 @@ test("documents aggregate billing and preserves pre-connection policy", async ({
await expect(goalOffer).toBeVisible();
await expect(goalOffer).toContainText("Pay half.");
await expect(goalOffer).toContainText("Choose Goal.");
await expect(goalOffer).toContainText("Coming soon");
await expect(goalOffer.locator(".billing-offer__number")).toHaveText("50%");
await expect(goalOffer.locator(".billing-offer__discount-label")).toHaveText("SAVE");
await expect(goalOffer.getByRole("link", { name: "Explore Goal Runs" })).toHaveCount(0);
await expect(goalOffer).toContainText("vs. One-shot-call");
await expect(goalOffer.locator('.billing-offer__badge svg[aria-hidden="true"]')).toBeVisible();
await expect(goalOffer.locator(".billing-offer__badge")).toHaveCount(0);
await expect(article).not.toContainText("Goal is currently half the price");
const successFeeNote = article.locator(".billing-note").filter({ hasText: "Task Success Fee" });
await expect(successFeeNote).toHaveText("Task Success Fee is an experimental, outcome-based fee shown for reference only. It is currently fully waived.");
Expand Down Expand Up @@ -274,9 +270,7 @@ test("documents aggregate billing and preserves pre-connection policy", async ({
await expect(examples.getByRole("row").filter({ hasText: example.mode }).getByRole("cell"))
.toHaveText([example.mode, ...example.totals]);
}
expect(billingMarkdown).toContain("Coming soon");
expect(billingMarkdown).toContain("Billing preview — coming soon");
expect(billingMarkdown).toContain("not yet in effect");
expect(billingMarkdown).not.toMatch(/preview|coming soon|upcoming|not yet in effect|under development|will pay/i);
expect(billingMarkdown.split(callingGuidance)).toHaveLength(2);
expect(billingMarkdown).toContain("It is currently fully waived.");
expect(billingMarkdown.match(/Task Success Fee/g)).toHaveLength(1);
Expand Down Expand Up @@ -317,11 +311,11 @@ test("compares prices with stacked bars and accessible fee breakdowns", async ({
await page.emulateMedia({ colorScheme });
await page.goto("/billing");
const offer = page.getByRole("region", { name: "Goal pricing offer" });
await expect(offer.locator(".billing-offer__badge")).toHaveText("Coming soon");
await expect(offer.locator(".billing-offer__badge")).toHaveCount(0);
await offer.screenshot({ path: testInfo.outputPath(`billing-offer-${width}-${colorScheme}.png`) });
const comparison = page.getByRole("figure", { name: /30-second call.*Domestic outbound/ });
await expect(comparison).toBeVisible();
await expect(comparison).toContainText("Hover or tap a segment for its rate · Preview");
await expect(comparison).toContainText("Hover or tap a segment for its rate");
await expect(comparison).toContainText("Model Fee · / 10s");
await expect(comparison).toContainText("Carrier Fee · / min");
await expect(comparison.locator(".billing-comparison__bar--oneshot")).toBeVisible();
Expand Down Expand Up @@ -383,6 +377,7 @@ test("compares prices with stacked bars and accessible fee breakdowns", async ({
await trigger.hover();
const tooltip = page.getByRole("tooltip");
await expect(tooltip).toBeVisible();
await expect(tooltip).not.toContainText(/preview|coming soon/i);
for (const value of [fee.label, `${fee.rate} / ${fee.unit}`, fee.usage, fee.cost]) await expect(tooltip).toContainText(value);
await expect(tooltip).not.toContainText(fee.other);
const popup = (await page.locator(".billing-cost-tooltip").boundingBox())!;
Expand Down Expand Up @@ -973,15 +968,16 @@ test("preserves authentication, webhook, and SDK guidance", async ({
).toHaveAttribute("href", "https://github.com/CALLE-AI/server-sdk-python");
});

test("recommends Goal Runs for scale with upcoming pricing on the Calls guide", async ({ page, request }, testInfo) => {
test("recommends Goal Runs for scale with live pricing on the Calls guide", async ({ page, request }, testInfo) => {
const markdown = await request.get("/calls.md");
expect(markdown.ok()).toBe(true);
const text = await markdown.text();
for (const phrase of ["quick integration, development, and testing", "batch and large-scale calling", "50% less", "upcoming billing model", "This pricing is not yet in effect."]) {
for (const phrase of ["quick integration, development, and testing", "batch and large-scale calling", "50% less", "which cost"]) {
expect(text).toContain(phrase);
}
expect(text).toContain("[Goal Runs](/goal-runs)");
expect(text).toContain("[upcoming billing model](/billing)");
expect(text).toContain("[Billing](/billing)");
expect(text).not.toMatch(/upcoming billing|not yet in effect|will cost|coming soon/i);
expect(text).toContain("POST /v2/calls");
expect(text).not.toContain("POST /v1/calls");
expect(text).toContain("Each request creates one phone task");
Expand All @@ -996,9 +992,9 @@ test("recommends Goal Runs for scale with upcoming pricing on the Calls guide",
await expect(guidance).toContainText("POST /v2/calls");
await expect(guidance).toContainText("quick integration, development, and testing");
await expect(guidance).toContainText("50% less");
await expect(guidance).toContainText("This pricing is not yet in effect.");
await expect(guidance).not.toContainText(/upcoming|not yet in effect|coming soon|will cost/i);
await expect(guidance.getByRole("link", { name: "Goal Runs", exact: true })).toHaveAttribute("href", "/goal-runs");
await expect(guidance.getByRole("link", { name: "upcoming billing model" })).toHaveAttribute("href", "/billing");
await expect(guidance.getByRole("link", { name: "Billing", exact: true })).toHaveAttribute("href", "/billing");
const guidanceBox = (await guidance.boundingBox())!;
const inputsBox = (await page.getByRole("heading", { level: 2, name: /^Request contract/ }).boundingBox())!;
expect(guidanceBox.y + guidanceBox.height).toBeLessThan(inputsBox.y);
Expand Down
Loading