feat(web): move Hire Us pricing to $400/hour metered billing (PRD 0002) (#102)

* feat(web): move Hire Us pricing to $400/hour metered billing (PRD 0002)

Replaces the $250/week retainer with a $400/hour rate billed against actual
hours, invoiced through CoinPay after the client approves them. A 10-hour
minimum engagement replaces the week as the unit of commitment.

The weekly price lived in 12 places, not the 3 the PRD listed: the front-page
Hire Us section, the Top-Level Pages list, /hire-us metadata, /pricing
(metadata, two FAQ answers, rate bullet), /about, llms.txt, skill.md, and the
Hire Us form success message.

Metered billing rather than a committed weekly block, because the old
"recurring CoinPay invoice" copy documented a mechanic that never existed:
/api/payments/create makes a single one-shot payment, not a subscription.

- coinpay-checkout derives amount_usd from hours x 400 instead of a hardcoded
  250, validates hours as quarter-hour increments at or above the minimum, and
  returns 422 before calling CoinPay on bad input. Payment metadata carries
  billing/hours/rate_usd_per_hour in place of interval.
- project-request returns a rate, billing mode, and minimum; no amount exists
  until hours are approved.
- CoinPay config block documents COINPAY_RATE_USD_PER_HOUR / COINPAY_BILLING /
  COINPAY_MINIMUM_HOURS instead of a weekly amount and interval.
- New real /terms route replacing the SPA stub: what is billable, the
  approve-then-invoice flow, the minimum, cancellation on one week's notice,
  and an explicit clause that existing engagements keep their terms until both
  sides agree in writing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(mcp): advance prd_next_id expectation to 0003 for PRD 0002

The standards test asserts prd_next_id against the live prd/ directory, so
adding prd/0002-hourly-hire-us-rate.md moves the next free id to 0003. This
assertion advances with every PRD added to the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-07-28 10:34:49 -07:00 committed by GitHub
parent da5f6f8381
commit eaf0a6162b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 446 additions and 43 deletions

View file

@ -173,18 +173,20 @@ describe("POST /api/hire-us/coinpay-checkout", () => {
expect(createCall?.[1]?.headers).toMatchObject({ authorization: "Bearer cp_test_key" });
expect(createBody).toMatchObject({
business_id: "business-123",
amount_usd: 250,
amount_usd: 4000,
payment_method: "both",
currency: "usdc_pol",
blockchain: "USDC_POL",
description: "LogicSRC Hire Us - $250/week",
description: "LogicSRC Hire Us - 10h @ $400/hour",
success_url: "https://logicsrc.test/hire-us?payment=success",
cancel_url: "https://logicsrc.test/hire-us?payment=cancelled",
redirect_url: "https://logicsrc.test/hire-us?payment=coinpay",
webhook_url: "https://logicsrc.test/api/webhooks/coinpay",
metadata: {
product: "logicsrc-hire-us",
interval: "week",
billing: "metered_hours",
hours: 10,
rate_usd_per_hour: 400,
source: "logicsrc.com/hire-us",
buyer_email: "buyer@example.com"
}
@ -241,7 +243,71 @@ describe("POST /api/hire-us/coinpay-checkout", () => {
const body = await response.json();
expect(response.status).toBe(201);
expect(body.payment.amount_usd).toBe(250);
expect(body.payment.amount_usd).toBe(4000);
});
it("prices the checkout from the approved hours", async () => {
process.env.COINPAY_API_KEY = "cp_test_key";
process.env.COINPAY_API_URL = "https://coinpayportal.example";
process.env.COINPAY_BUSINESS_ID = "business-123";
process.env.COINPAY_ELIGIBILITY_MERCHANT_ID = "merchant-123";
process.env.COINPAY_HIRE_US_BLOCKCHAIN = "USDC_POL";
const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
const url = typeof input === "string" ? input : input.toString();
if (url.includes("/api/payments/merchant-eligibility")) {
return jsonResponse({ success: true, accepts_card: true, accepts_crypto: true, chains: ["USDC_POL"] });
}
return jsonResponse({ success: true, payment: { id: "pay_123", amount_usd: 10100 } }, 201);
});
const response = await coinpayCheckout(
new NextRequest("http://localhost/api/hire-us/coinpay-checkout", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ hours: 25.25 })
})
);
const body = await response.json();
const createCall = fetchMock.mock.calls.find(([input]) =>
(typeof input === "string" ? input : input.toString()).includes("/api/payments/create")
);
const createBody = JSON.parse((createCall?.[1]?.body as string) ?? "{}");
expect(response.status).toBe(201);
expect(createBody.amount_usd).toBe(10100);
expect(createBody.description).toBe("LogicSRC Hire Us - 25.25h @ $400/hour");
expect(createBody.metadata).toMatchObject({ billing: "metered_hours", hours: 25.25, rate_usd_per_hour: 400 });
expect(body.payment).toMatchObject({ amount_usd: 10100, hours: 25.25, rate_usd_per_hour: 400 });
});
it("rejects hours below the minimum engagement or off the quarter-hour", async () => {
process.env.COINPAY_API_KEY = "cp_test_key";
process.env.COINPAY_API_URL = "https://coinpayportal.example";
process.env.COINPAY_BUSINESS_ID = "business-123";
process.env.COINPAY_ELIGIBILITY_MERCHANT_ID = "merchant-123";
const fetchMock = vi.spyOn(globalThis, "fetch");
for (const hours of [9.75, 12.3, "many", -40]) {
const response = await coinpayCheckout(
new NextRequest("http://localhost/api/hire-us/coinpay-checkout", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ hours })
})
);
const body = await response.json();
expect(response.status).toBe(422);
expect(body).toEqual({
success: false,
error: "Approved hours must be a quarter-hour increment of at least 10"
});
}
expect(fetchMock).not.toHaveBeenCalled();
});
it("does not create checkout when no payment rail is available", async () => {
@ -290,7 +356,13 @@ describe("POST /api/hire-us/project-request", () => {
expect(response.status).toBe(202);
expect(body).toMatchObject({
success: true,
request: { status: "pending_acceptance", amount_usd: 250, interval: "week", invoice: "created_after_acceptance" }
request: {
status: "pending_acceptance",
rate_usd_per_hour: 400,
billing: "metered_hours",
minimum_hours: 10,
invoice: "created_after_acceptance"
}
});
expect(body.request.id).toMatch(/^hire_/);
});

View file

@ -39,14 +39,17 @@ test.describe("LogicSRC PWA", () => {
await page.goto("/hire-us");
await expect(page.getByRole("heading", { name: "Hire Us", exact: true })).toBeVisible();
await expect(page.locator(".price-row strong", { hasText: "$250" })).toBeVisible();
await expect(page.getByText("per week")).toBeVisible();
await expect(page.locator(".price-row strong", { hasText: "$400" })).toBeVisible();
await expect(page.getByText("per hour")).toBeVisible();
await expect(page.getByText("Ten-hour minimum engagement")).toBeVisible();
await expect(page.getByText("open infrastructure and open specs for AI agent systems")).toBeVisible();
await expect(page.getByRole("button", { name: "Request review" })).toBeVisible();
await expect(page.getByPlaceholder("you@example.com")).toBeVisible();
await expect(page.getByPlaceholder("Describe the agent workflow")).toBeVisible();
await expect(page.getByText("without exposing merchant credentials to the browser")).toBeVisible();
await expect(page.getByText("COINPAY_PRODUCT=logicsrc-hire-us")).toBeVisible();
await expect(page.getByText("COINPAY_RATE_USD_PER_HOUR=400")).toBeVisible();
await expect(page.getByText("COINPAY_BILLING=metered_hours")).toBeVisible();
await expect(page.getByText("COINPAY_STATUS=pending_acceptance")).toBeVisible();
});

View file

@ -8,8 +8,9 @@ import { HomeInteractivity } from "@/components/home-interactivity";
// scrolled to the matching section. We preserve those URLs (they are canonical
// in sitemap.xml) by rendering the same page for each known route and 404ing
// anything else.
// /about and /docs are now real routes (app/about, app/docs); the rest still
// render the homepage SPA scrolled to their section.
// /about, /docs, /pricing, and /terms are now real routes (app/about, app/docs,
// app/pricing, app/terms); the rest still render the homepage SPA scrolled to
// their section.
const ROUTE_META: Record<string, { title: string; description: string }> = {
openspec: {
title: "LogicSRC vs OpenSpec.dev · LogicSRC",
@ -21,9 +22,8 @@ const ROUTE_META: Record<string, { title: string; description: string }> = {
},
"hire-us": {
title: "Hire Us · LogicSRC",
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $250/week for accepted work, paid via CoinPay.",
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $400/hour for accepted work, paid via CoinPay.",
},
terms: { title: "Terms · LogicSRC", description: "LogicSRC terms of use." },
privacy: { title: "Privacy · LogicSRC", description: "LogicSRC privacy notes." },
"agent-swarm": {
title: "AgentSwarm · LogicSRC",

View file

@ -84,7 +84,7 @@ export default function AboutPage(): ReactNode {
<h3>Work with us</h3>
<p>
Profullstack implements LogicSRC-based systems at $250/week for
Profullstack implements LogicSRC-based systems at $400/hour for
accepted work, paid via CoinPay. See <a href="/hire-us">Hire Us</a>.
</p>
</div>

View file

@ -4,8 +4,23 @@ import { choosePaymentRail, fetchMerchantEligibility, parseJson } from "@/lib/co
export const dynamic = "force-dynamic";
// POST /api/hire-us/coinpay-checkout — create a $250/week CoinPay checkout for
// the Hire Us plan, choosing card/crypto/both based on merchant eligibility.
// POST /api/hire-us/coinpay-checkout — create a CoinPay checkout for approved
// Hire Us hours at $400/hour, choosing card/crypto/both based on merchant
// eligibility. Billing is metered: the caller supplies the approved hours and the
// amount is derived from them, never a fixed recurring figure.
const RATE_USD_PER_HOUR = 400;
const MINIMUM_HOURS = 10;
// Hours are quoted in quarter-hour increments; anything finer is a rounding
// artifact rather than a real billing unit.
function parseHours(value: unknown): number | null {
const hours = typeof value === "number" ? value : Number(value);
if (!Number.isFinite(hours) || hours < MINIMUM_HOURS) return null;
const quarters = Math.round(hours * 4);
if (Math.abs(hours * 4 - quarters) > 1e-9) return null;
return quarters / 4;
}
export async function POST(request: NextRequest) {
const apiKey = process.env.COINPAY_API_KEY;
const eligibilityApiKey = process.env.COINPAY_ELIGIBILITY_API_KEY || process.env.COINPAY_AGENT_API_KEY || apiKey;
@ -22,6 +37,19 @@ export async function POST(request: NextRequest) {
try {
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
const buyerEmail = typeof body.email === "string" ? body.email.trim().slice(0, 160) : "";
const hours = body.hours === undefined ? MINIMUM_HOURS : parseHours(body.hours);
if (hours === null) {
return json(
{
success: false,
error: `Approved hours must be a quarter-hour increment of at least ${MINIMUM_HOURS}`
},
422
);
}
const amountUsdDue = Math.round(hours * RATE_USD_PER_HOUR * 100) / 100;
const eligibility = await fetchMerchantEligibility(apiUrl, eligibilityApiKey, eligibilityMerchantId);
const paymentRail = choosePaymentRail(eligibility, blockchain);
@ -37,18 +65,20 @@ export async function POST(request: NextRequest) {
},
body: JSON.stringify({
business_id: businessId,
amount_usd: 250,
amount_usd: amountUsdDue,
payment_method: paymentRail.method,
currency: paymentRail.currency,
...(paymentRail.blockchain ? { blockchain: paymentRail.blockchain } : {}),
description: "LogicSRC Hire Us - $250/week",
description: `LogicSRC Hire Us - ${hours}h @ $${RATE_USD_PER_HOUR}/hour`,
success_url: `${publicUrl}/hire-us?payment=success`,
cancel_url: `${publicUrl}/hire-us?payment=cancelled`,
redirect_url: `${publicUrl}/hire-us?payment=coinpay`,
webhook_url: `${publicUrl}/api/webhooks/coinpay`,
metadata: {
product: "logicsrc-hire-us",
interval: "week",
billing: "metered_hours",
hours,
rate_usd_per_hour: RATE_USD_PER_HOUR,
source: "logicsrc.com/hire-us",
...(buyerEmail ? { buyer_email: buyerEmail } : {})
}
@ -70,13 +100,15 @@ export async function POST(request: NextRequest) {
}
const payment = (payload.payment as Record<string, unknown>) || {};
const amountUsd = Number(payment.amount_usd ?? payment.amount ?? 250);
const amountUsd = Number(payment.amount_usd ?? payment.amount ?? amountUsdDue);
return json(
{
success: true,
payment: {
id: payment.id,
amount_usd: Number.isFinite(amountUsd) ? amountUsd : 250,
amount_usd: Number.isFinite(amountUsd) ? amountUsd : amountUsdDue,
hours,
rate_usd_per_hour: RATE_USD_PER_HOUR,
payment_method: payment.stripe_checkout_url ? "card" : paymentRail.method,
currency: payment.currency ?? payment.blockchain ?? paymentRail.blockchain ?? paymentRail.currency,
crypto_amount: payment.amount_crypto ?? payment.crypto_amount ?? null,

View file

@ -3,8 +3,12 @@ import { json } from "@/lib/http";
export const dynamic = "force-dynamic";
// POST /api/hire-us/project-request — accept a Hire Us project request before a
// recurring CoinPay invoice is created (invoice is created after acceptance).
// POST /api/hire-us/project-request — accept a Hire Us project request before any
// CoinPay invoice is created. Hire Us bills metered hours at $400/hour, so there is
// no amount until we accept the project and hours are approved.
const RATE_USD_PER_HOUR = 400;
const MINIMUM_HOURS = 10;
export async function POST(request: NextRequest) {
try {
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
@ -20,7 +24,7 @@ export async function POST(request: NextRequest) {
id: requestId,
contact,
project_length: project.length,
plan: "250/week",
plan: "400/hour",
invoice: "pending_acceptance"
});
@ -30,8 +34,9 @@ export async function POST(request: NextRequest) {
request: {
id: requestId,
status: "pending_acceptance",
amount_usd: 250,
interval: "week",
rate_usd_per_hour: RATE_USD_PER_HOUR,
billing: "metered_hours",
minimum_hours: MINIMUM_HOURS,
invoice: "created_after_acceptance"
}
},

View file

@ -24,7 +24,7 @@ export function GET(): Response {
## Company & legal
- [About](${SITE_URL}/about): What LogicSRC is and who maintains it (Profullstack, Inc.).
- [Hire Us](${SITE_URL}/hire-us): Implementation help at $250/week for accepted LogicSRC work.
- [Hire Us](${SITE_URL}/hire-us): Implementation help at $400/hour for accepted LogicSRC work.
- [Terms](${SITE_URL}/terms)
- [Privacy](${SITE_URL}/privacy)
`;

View file

@ -5,7 +5,7 @@ import { SiteShell } from "@/components/site-shell";
export const metadata: Metadata = {
title: "Pricing · LogicSRC",
description:
"LogicSRC the open specification, schemas, SDKs, and CLI are free and open source. Implementation help is $250/week for accepted work, paid via CoinPay.",
"LogicSRC the open specification, schemas, SDKs, and CLI are free and open source. Implementation help is $400/hour for accepted work, paid via CoinPay.",
alternates: { canonical: "/pricing" },
};
@ -16,7 +16,7 @@ const FAQ: Array<{ q: string; a: string }> = [
},
{
q: "How does pricing work?",
a: "The standard is free. If you want Profullstack to build a LogicSRC-based system for you, implementation work is billed at $250/week for accepted work, paid via a CoinPay recurring invoice.",
a: "The standard is free. If you want Profullstack to build a LogicSRC-based system for you, implementation work is billed at $400/hour against actual hours worked, invoiced through CoinPay after you approve them. The minimum engagement is 10 hours.",
},
{
q: "Who is LogicSRC for?",
@ -24,7 +24,7 @@ const FAQ: Array<{ q: string; a: string }> = [
},
{
q: "How do I pay or get started?",
a: "Read the docs and adopt the schemas for free, or submit a project through the Hire Us form. Accepted projects are invoiced weekly via CoinPay.",
a: "Read the docs and adopt the schemas for free, or submit a project through the Hire Us form. Accepted projects are invoiced for approved hours via CoinPay.",
},
];
@ -61,9 +61,11 @@ export default function PricingPage(): ReactNode {
CLI, TUI, and reference plugins are open source.
</li>
<li>
<strong>Implementation $250/week.</strong> Profullstack builds
LogicSRC-based systems for accepted projects, billed weekly via
CoinPay. See <a href="/hire-us">Hire Us</a>.
<strong>Implementation $400/hour.</strong> Profullstack builds
LogicSRC-based systems for accepted projects, billed against actual
hours worked and invoiced via CoinPay once you approve them.
Ten-hour minimum engagement. See <a href="/hire-us">Hire Us</a> and{" "}
<a href="/terms">Terms</a>.
</li>
</ul>

View file

@ -25,7 +25,7 @@ Base URL: ${SITE_URL}
- Read the LogicSRC coordination schemas and conventions.
- Compare LogicSRC with OpenSpec.dev.
- Request paid implementation help via the Hire Us flow (${SITE_URL}/hire-us),
billed at $250/week for accepted work and paid through CoinPay.
billed at $400/hour for accepted work and paid through CoinPay.
## Notes

View file

@ -0,0 +1,128 @@
import type { ReactNode } from "react";
import type { Metadata } from "next";
import { SiteShell } from "@/components/site-shell";
export const metadata: Metadata = {
title: "Terms · LogicSRC",
description:
"Terms of engagement for LogicSRC: the specification and tooling are open source and free; Profullstack implementation work is billed at $400/hour against approved hours, with a 10-hour minimum.",
alternates: { canonical: "/terms" },
};
export default function TermsPage(): ReactNode {
return (
<SiteShell active="Terms">
<article className="band" style={{ maxWidth: "48rem" }}>
<div className="section-head">
<h2>Terms</h2>
<p>
Two separate things live on this site: an open standard anyone may
use, and paid implementation work from Profullstack, Inc. These terms
cover both.
</p>
</div>
<div className="blog-content" style={{ lineHeight: 1.7, marginTop: "1.5rem" }}>
<h3>The standard is free</h3>
<p>
The LogicSRC specification, JSON schemas, SDKs, CLI, TUI, and
reference plugins are open source under the project license. There is
no license fee, no per-seat charge, and no obligation to hire us in
order to implement the standard. Nothing on this page restricts your
use of the spec.
</p>
<h3>Rate</h3>
<p>
Profullstack implementation work is billed at{" "}
<strong>$400 per hour</strong>. One rate applies to all
implementation work specs, CLIs, SDKs, MCP resources, APIs, PWAs,
and provider-neutral plugin surfaces. There are no tiers, role-based
rates, or volume discounts.
</p>
<h3>What is billable</h3>
<p>
Billable hours are hours spent on your project: design, spec work,
implementation, review, debugging, integration, and deployment, plus
meetings and written communication about the work. Time is recorded
in quarter-hour increments.
</p>
<p>
The following are not billed: the initial scoping conversation,
preparing your invoice, and time spent fixing defects in work we have
already delivered and you have already paid for.
</p>
<h3>How you are invoiced</h3>
<p>
Billing is metered against actual hours worked, not a subscription.
After we accept a project, we send you a record of hours worked. Once
you approve those hours, we issue a CoinPay invoice for exactly that
amount at $400/hour. You are never charged for hours you have not
seen and approved.
</p>
<p>
Invoices are payable on receipt via CoinPay, by card or by supported
cryptocurrency. Work may pause on invoices unpaid after 14 days.
</p>
<h3>Minimum engagement</h3>
<p>
The minimum engagement is <strong>10 hours</strong>. Engagements
smaller than this do not cover the cost of scoping and context, so we
will decline them rather than quote them.
</p>
<h3>Cancellation</h3>
<p>
Either side may end an engagement at any time with one week&apos;s
written notice. You pay for hours already worked and approved,
including hours worked during the notice period; nothing further is
owed. We do not bill a cancellation fee and we do not hold unused
committed hours. Work product produced by hours you have paid for is
yours to keep.
</p>
<h3>Existing engagements</h3>
<p>
Clients engaged under a prior pricing model keep their existing terms
until both sides agree in writing to move to the hourly rate. This
page does not reprice work already underway.
</p>
<h3>Estimates</h3>
<p>
Any estimate of total hours is an estimate, not a fixed-price quote.
If the work looks likely to exceed an estimate we will tell you
before the additional hours are worked, so you can rescope or stop.
</p>
<h3>Acceptable use</h3>
<p>
We build auditable, portable systems in the open. We decline work
intended to deceive users, evade legal obligations, or produce
systems that cannot be inspected by the people they affect.
</p>
<h3>Reference implementations</h3>
<p>
Reference implementations published under the LogicSRC project exist
to prove the specification is usable. They are provided as-is,
without warranty, and are not a hosted product or a support
commitment. Paid engagements are governed by the terms above, not by
the license of any reference implementation.
</p>
<h3>Questions</h3>
<p>
Submit a project through the <a href="/hire-us">Hire Us</a> form, or
see <a href="/pricing">Pricing</a> for a summary. These terms may
change; the version on this page at the time your engagement starts
is the one that applies to it.
</p>
</div>
</article>
</SiteShell>
);
}

View file

@ -45,7 +45,7 @@ export function HomeInteractivity(): null {
}
result.replaceChildren(
buildParagraph("Request received. If it is a fit, we will send a $250/week recurring CoinPay invoice.")
buildParagraph("Request received. If it is a fit, we will scope the work and invoice approved hours at $400/hour via CoinPay.")
);
} catch (error) {
result.replaceChildren(

View file

@ -69,9 +69,9 @@ const pages = [
{ id: "blog", title: "Blog", detail: "Project notes for LogicSRC, AgentSwarm, AgentByte, OpenSpec workflows, and reference implementations." },
{ id: "openspec", title: "OpenSpec", detail: "Comparison and compatibility notes for OpenSpec.dev-style repo-local specs, proposals, tasks, and deltas." },
{ id: "credential-sharing", title: "Credential Sharing", detail: "Open replacement architecture for portable secret sync across .env, Doppler, Railway variables, GitHub Secrets, and future providers." },
{ id: "hire-us", title: "Hire Us", detail: "$250/week LogicSRC work on open infrastructure, specs, AI agent workflows, and reference implementations paid through CoinPay after project acceptance." },
{ id: "hire-us", title: "Hire Us", detail: "$400/hour LogicSRC work on open infrastructure, specs, AI agent workflows, and reference implementations paid through CoinPay after project acceptance." },
{ id: "about", title: "About", detail: "LogicSRC is the Profullstack open specification project for human and AI agent coordination." },
{ id: "terms", title: "Terms", detail: "Draft terms will cover acceptable use, reference implementation boundaries, and hosted-product responsibilities." },
{ id: "terms", title: "Terms", detail: "Terms of engagement: the $400/hour rate, what is billable, how approved hours are invoiced, the 10-hour minimum, cancellation, acceptable use, and reference implementation boundaries." },
{ id: "privacy", title: "Privacy", detail: "Draft privacy notes will cover telemetry, audit events, identity data, and hosted-product data boundaries." }
];
@ -358,7 +358,7 @@ logicsrc credentials rollback --run &lt;runId&gt;</code></pre>
<section id="hire-us" class="band hire-us">
<div class="section-head">
<h2>Hire Us</h2>
<p>$250/week for accepted LogicSRC work using open infrastructure and open specs for AI agent systems.</p>
<p>$400/hour for accepted LogicSRC work using open infrastructure and open specs for AI agent systems.</p>
</div>
<div class="hire-layout">
<article class="hire-panel">
@ -366,9 +366,10 @@ logicsrc credentials rollback --run &lt;runId&gt;</code></pre>
<h3>Open-spec AI agent implementation help</h3>
<p>Hire us to turn agent ideas into portable LogicSRC specs, CLIs, SDKs, MCP resources, PWAs, APIs, and provider-neutral plugin workflows. We prioritize auditable contracts, repo-local artifacts, and integrations that can move between model providers and infrastructure.</p>
<div class="price-row">
<strong>$250</strong>
<span>per week</span>
<strong>$400</strong>
<span>per hour</span>
</div>
<p class="price-terms">Billed against actual hours worked, invoiced after you approve them. Ten-hour minimum engagement; cancel any time with one week&rsquo;s notice and pay only for hours already approved. See <a href="/terms">Terms</a>.</p>
<form id="project-request-form" class="project-request-form">
<label>
<span>Contact</span>
@ -395,12 +396,13 @@ logicsrc credentials rollback --run &lt;runId&gt;</code></pre>
`).join("")}
</div>
<article id="coinpay-setup" class="coinpay-panel">
<h3>CoinPay recurring invoice</h3>
<p>After we accept the project, we create a recurring CoinPay invoice for the weekly plan without exposing merchant credentials to the browser.</p>
<h3>CoinPay hourly invoice</h3>
<p>After we accept the project, we invoice approved hours at $400/hour through CoinPay without exposing merchant credentials to the browser.</p>
<pre><code>COINPAY_ORG=profullstack
COINPAY_PRODUCT=logicsrc-hire-us
COINPAY_AMOUNT_USD=250
COINPAY_INTERVAL=week
COINPAY_RATE_USD_PER_HOUR=400
COINPAY_BILLING=metered_hours
COINPAY_MINIMUM_HOURS=10
COINPAY_STATUS=pending_acceptance</code></pre>
</article>
</div>

View file

@ -484,6 +484,17 @@ pre {
font-weight: 800;
}
.price-terms {
color: #b5beb2;
font-size: 0.85rem;
line-height: 1.5;
margin: -0.5rem 0 0;
}
.price-terms a {
color: inherit;
}
.cta-row {
display: flex;
flex-wrap: wrap;

View file

@ -202,7 +202,8 @@ describe("MCP: OpenPRD", () => {
it("reports the next free id and the allowed lifecycle moves", async () => {
const client = await connect();
expect(toolText(await client.callTool({ name: "prd_next_id", arguments: {} }))).toBe("0002");
// Asserted against the live prd/ directory, so this advances with every PRD added.
expect(toolText(await client.callTool({ name: "prd_next_id", arguments: {} }))).toBe("0003");
const moves = await client.callTool({ name: "prd_next_statuses", arguments: { ref: "0001" } });
const payload = JSON.parse(toolText(moves)) as { status: string; allowedNext: string[] };

View file

@ -0,0 +1,146 @@
---
openprd: "0.2"
id: "0002"
title: Move Hire Us pricing from a weekly retainer to an hourly rate
status: Accepted
authors:
- anthony@profullstack.com
created: 2026-07-28
updated: 2026-07-28
repo: https://github.com/profullstack/logicsrc
implementation: apps/logicsrc-web
tags: [pricing, site, billing]
---
## Problem
The Hire Us surface on logicsrc.com prices standards work at $250/week, paid through a
recurring CoinPay invoice after project acceptance. That number reads as a token retainer
rather than a rate for senior open-spec implementation work — schemas, CLIs, SDKs, MCP
resources, and provider-neutral plugin surfaces. It anchors every inbound conversation at a
price that cannot cover the work, and it selects for clients who are shopping on price rather
than on the standard.
The replacement is $400/hour. This is not a bump to an existing hourly number; it is a change
of pricing *model*, which touches page copy, the payment configuration, and the invoicing
mechanics that currently assume a fixed weekly amount.
## Goals
- Every public price on logicsrc.com states one rate, in one unit, with no stale $250/week
copy left behind on any surface.
- Inbound Hire Us inquiries arrive already anchored to a senior rate, so the pricing
conversation is about scope rather than about the number.
- Billing can actually execute the new model: an accepted project produces a correct invoice
without manual repair.
- The change is reversible and auditable — the reasoning survives in the repo, not in a Slack
thread.
## Non-Goals
- Repricing the standard itself. LogicSRC schemas, specs, and reference implementations stay
open and free; this covers implementation services only.
- Building time tracking. Hourly billing needs hours captured, but that is an operational
process for now, not a product to build.
- Replacing CoinPay or adding a second payment provider.
- Publishing a rate card with tiers, discounts, or role-based pricing. One rate, one line.
- Migrating anyone currently engaged at $250/week. Handled case by case, not by this PRD.
## Users
- **Prospective clients** evaluating whether to hire Profullstack for LogicSRC work — mostly
founders and engineering leads arriving from the spec pages, who read the price before they
read anything else.
- **Profullstack**, as the party quoting, invoicing, and collecting.
- **Existing clients** on the weekly plan, who must not be silently repriced.
## Decisions
Two questions blocked this PRD at drafting time. Both were resolved before implementation:
- **The rate is $400/hour, confirmed deliberately.** Against $250/week this is roughly a 64x
change at a 40-hour week. The magnitude is the point: the weekly figure was a token retainer,
not a rate, and the new number is intended to filter out engagements too small to scope.
- **Billing is metered against actual hours, not a committed weekly block.** This matches how
the code already works — `/api/payments/create` creates a single one-shot payment, never a
recurring subscription, so the previous "recurring invoice" copy documented a mechanic that
did not exist. Metered billing also avoids the committed-block failure mode, which is a
weekly rate wearing an hourly label. A **10-hour minimum engagement** replaces the week as
the unit of commitment.
## Requirements
- R1 [P0] The Hire Us section heading states the new rate and unit, replacing "$250/week for
accepted LogicSRC work".
- R2 [P0] The price display block shows $400 with the unit "per hour".
- R3 [P0] The `/hire-us` entry in the Top-Level Pages list is updated; it repeated the weekly
figure independently of the section above it.
- R4 [P0] The `/hire-us` page body and its meta description are updated.
- R5 [P0] The CoinPay configuration block reflects the new model. `COINPAY_AMOUNT_USD=250` and
`COINPAY_INTERVAL=week` are both wrong under hourly pricing and are replaced by a rate, a
billing mode, and a minimum rather than renumbered.
- R6 [P0] A repo-wide search for `250`, `per week`, and `/week` returns no remaining pricing
references before the change is considered done.
- R7 [P1] The site states what an hour is billed against — metered actual hours, invoiced after
the client approves them — so the invoice mechanic is legible before a client asks.
- R8 [P1] Minimum engagement and cancellation terms are stated, since removing the weekly
cadence also removes the implicit unit of commitment.
- R9 [P2] Terms of engagement are documented at `/terms` rather than only in marketing copy.
Surfaces the original draft did not list, but which carried the weekly price and were therefore
in scope for R6: `/pricing` (metadata, two FAQ answers, and the rate bullet), `/about`,
`llms.txt`, `skill.md`, the Hire Us form's success message, and both the contract and e2e tests.
## UX Notes
The price appears in several places across the front page, `/pricing`, `/about`, and the
machine-readable surfaces; they are separate strings and will drift if edited one at a time.
Treat the set as one change.
The CoinPay block is rendered as example configuration, so it reads as documentation of how
billing actually works. A weekly interval next to an hourly rate is worse than a stale price —
it looks like the system does not do what the copy says.
The stated rate sits next to what it buys. The existing four capability cards (workflow specs,
reference implementations, integration hardening, open infrastructure) already do that work and
are unchanged.
## Implementation Notes
- `src/lib/page-markup.ts` — section heading, price row, a new `.price-terms` line carrying the
minimum and cancellation summary, the CoinPay config block, and the Top-Level Pages entries
for both Hire Us and Terms.
- `src/app/api/hire-us/coinpay-checkout/route.ts` — the amount is now derived as
`hours × $400` rather than hardcoded. Hours are validated as quarter-hour increments at or
above the 10-hour minimum, defaulting to the minimum when omitted; invalid hours return 422
before any call to CoinPay. Payment metadata carries `billing`, `hours`, and
`rate_usd_per_hour` in place of `interval`.
- `src/app/api/hire-us/project-request/route.ts` — returns a rate, billing mode, and minimum
instead of a fixed `amount_usd`/`interval` pair, since no amount exists until hours are
approved.
- `src/app/terms/page.tsx` — new real route, replacing the `/terms` stub that rendered the
homepage SPA. The stub's entry was removed from the catch-all's `ROUTE_META`.
## Success Metrics
- Zero occurrences of the weekly price across the site and repo after the change, verified by
grep rather than by reading.
- First accepted project under the new model invoices correctly on the first attempt, with no
manual adjustment to the CoinPay invoice.
- Inbound inquiries that reach a scoping conversation do not open by disputing the rate.
- No existing engagement is repriced without explicit agreement.
## Risks & Open Questions
- **Hours are not currently tracked.** Metered billing won, and there is no capture mechanism.
`/terms` now defines what is billable (project work, meetings, and written communication in
quarter-hour increments; not scoping calls, invoicing, or warranty fixes), so the definition
is settled even though the tooling is not. Capturing hours is an operational process until it
is worth building.
- **Client mix will change.** An hourly rate at this level filters out the small experimental
engagements the weekly price attracted. This is the intent, recorded here as a decision
rather than left to be discovered.
- **Existing weekly clients** keep their terms until both sides agree in writing to move, per
`/terms`. Whether any active weekly engagements exist is still unknown.
- **Author attribution** is assumed from the CoinPay org configuration and should be corrected
if wrong.

View file

@ -12,3 +12,4 @@ Status lives in each file's front-matter and is the source of truth:
| ID | Title | Status | Tags |
| --- | --- | --- | --- |
| [0001](./0001-add-logicsrc-openontology-spec.md) | Add the LogicSRC OpenOntology specification | Draft | openontology, ontology, knowledge-graph, agents, mcp, schemas |
| [0002](./0002-hourly-hire-us-rate.md) | Move Hire Us pricing from a weekly retainer to an hourly rate | Accepted | pricing, site, billing |