SudoMock
GET

Plans

Retrieve all available subscription plans with pricing, credit allowances, and concurrency limits. Use this to build pricing pages or validate plan capabilities.

GET/api/v1/packages/plans
Public endpoint

No Authentication Required

This is a public endpoint. No API key or authentication headers are needed. Plans are returned sorted by price_monthly ascending.

Request

Send a GET request with no parameters. The endpoint returns every active plan row, which is more than the plans shown on the pricing page: the same credit volume can ship in several SKUs that differ only in their PSD template ceiling. Match on slug. Array position and array length are not stable, and neither is the set of slugs.

Headers

No headers required. The endpoint is publicly accessible.

Code Examples

Get Plans
bash
1curl -X GET "https://api.sudomock.com/api/v1/packages/plans"

Response

Success Response

200OK
Response 200 OK (excerpt)
1{
2 "plans": [
3 {
4 "id": "a1b2c3d4-...",
5 "name": "Free",
6 "slug": "free",
7 "tier": "free",
8 "description": "Trial state of pay as you go. 500 credits, granted once.",
9 "price_monthly": 0,
10 "price_yearly": 0,
11 "credits_per_month": 500,
12 "max_concurrent_requests": 1,
13 "max_concurrent_uploads": 1,
14 "psd_limit": 5,
15 "stripe_price_id": null,
16 "stripe_price_id_yearly": null
17 },
18 {
19 "id": "e5f6g7h8-...",
20 "name": "Starter 5K",
21 "slug": "starter-5k",
22 "tier": "starter",
23 "description": "For small stores and side projects",
24 "price_monthly": 25,
25 "price_yearly": 250,
26 "credits_per_month": 5000,
27 "max_concurrent_requests": 3,
28 "max_concurrent_uploads": 2,
29 "psd_limit": 150,
30 "stripe_price_id": "<opaque-checkout-price-id>",
31 "stripe_price_id_yearly": "<opaque-checkout-price-id>"
32 },
33 {
34 "id": "i9j0k1l2-...",
35 "name": "Pro 25K",
36 "slug": "pro-25k",
37 "tier": "pro",
38 "description": "For growing businesses with high volume",
39 "price_monthly": 89,
40 "price_yearly": 890,
41 "credits_per_month": 25000,
42 "max_concurrent_requests": 10,
43 "max_concurrent_uploads": 5,
44 "psd_limit": 500,
45 "stripe_price_id": "<opaque-checkout-price-id>",
46 "stripe_price_id_yearly": "<opaque-checkout-price-id>"
47 },
48 {
49 "id": "m3n4o5p6-...",
50 "name": "Scale 50K",
51 "slug": "scale-50k",
52 "tier": "scale",
53 "description": "High-volume production and platform workflows",
54 "price_monthly": 199,
55 "price_yearly": 1990,
56 "credits_per_month": 50000,
57 "max_concurrent_requests": 25,
58 "max_concurrent_uploads": 10,
59 "psd_limit": null,
60 "stripe_price_id": "<opaque-checkout-price-id>",
61 "stripe_price_id_yearly": "<opaque-checkout-price-id>"
62 }
63 ]
64}

This example is an excerpt

Four representative rows are shown. The live response carries every active plan, including additional pro SKUs that share the 25,000 credit volume and differ only in psd_limit. Read the fields you need off the row whose slug you matched, and ignore rows you do not recognize rather than failing on them.

The free row is a trial state, not a monthly allowance

The row with slug: "free" reports credits_per_month: 500 because it reuses the same column as the paid rows. Those 500 credits are granted once at signup and never renew. An account sitting on this row is in trial until a card is verified and the balance is funded: renders are watermarked and output width is capped at 1,024 px. See Error Handling for the 402 responses those limits produce.

Response Fields

plansarrayRequired

Array of all active subscription plans, sorted by price_monthly ascending

plans[].idstringRequired

Unique plan identifier (UUID)

plans[].namestringRequired

Display name of the plan (e.g., 'Starter 5K', 'Pro 25K', 'Scale 50K')

plans[].slugstringRequired

URL-friendly identifier for programmatic use (e.g., 'starter-5k', 'pro-25k')

plans[].tierenumRequired

Plan tier level: free, starter, pro, or scale

plans[].descriptionstring

Human-readable plan description (may be null)

plans[].price_monthlynumberRequired

Monthly price in USD. Paid plans start at $25/mo

plans[].price_yearlynumberRequired

Yearly price in USD (discounted annual billing)

plans[].credits_per_monthintegerRequired

Render credits included per billing period. On the free row this is the one-time signup grant of 500 credits, not a recurring allowance: it is issued once and never renews.

plans[].max_concurrent_requestsintegerRequired

Maximum number of parallel render requests allowed. 1 on free, 3 on starter, 10 on pro, 25 on scale.

plans[].max_concurrent_uploadsintegerRequired

Maximum number of parallel PSD uploads allowed. 1 on free, 2 on starter, 5 on pro, 10 on scale. This is a separate budget from max_concurrent_requests and is always the smaller of the two.

plans[].psd_limitinteger

How many PSD templates the account may store on this plan. Null means unlimited. Uploading past the ceiling returns psd_limit_reached; templates already stored keep rendering.

plans[].stripe_price_idstring

Opaque monthly checkout price identifier. Null on the free row, which has nothing to check out.

plans[].stripe_price_id_yearlystring

Opaque annual checkout price identifier for the same plan. Null on the free row, and null on any plan not sold annually.

Plan Tiers

The tier field takes one of four values. Three of them are subscriptions. The fourth, free, is the trial state an account starts in, not something anyone buys:

TierStarting PriceBest For
free$0 in trial, then $0.10/renderPay as you go. In trial: 500 credits granted once, watermarked output, 1,024 px cap, 5 stored templates. Once funded: no watermark, no width cap, 500 stored templates, 25 parallel renders
starter$25/moSmall stores, side projects, testing integrations
pro$89/moGrowing businesses with higher volume needs
scale$199/moHigh-volume production, enterprise workflows

The free row is the catalogue entry, not a funded account's limits

GET /packages/plans returns the plan catalogue exactly as stored, so the free row always reports psd_limit: 5 and max_concurrent_requests: 1. A pay-as-you-go account that has verified a card and funded its balance sits on that same row but is enforced at 500 templates and 25 parallel renders. Do not render this row as a funded customer's ceiling.

Nor as a permanent one. The enforced ceilings follow the funding: they hold while the balance is positive and for 90 days after the last top-up, then the account is enforced at the trial numbers again. Neither state is visible in this endpoint, so a dashboard that caches the funded ceilings will keep showing them after they lapse.

Annual Billing Saves ~17%

Use the price_yearly field to display annual pricing to your users. Annual billing provides significant savings compared to monthly.

Common Use Cases

Building a Pricing Page

Fetch plans dynamically to keep your pricing page always in sync:

Dynamic Pricing Page
1const response = await fetch("https://api.sudomock.com/api/v1/packages/plans");
2const { plans } = await response.json();
3
4// Filter to paid plans for your pricing page
5const paidPlans = plans.filter(p => p.tier !== "free");
6
7paidPlans.forEach(plan => {
8 console.log(plan.name);
9 console.log(` Monthly: $${plan.price_monthly}`);
10 console.log(` Yearly: $${plan.price_yearly} ($${(plan.price_yearly / 12).toFixed(2)}/mo)`);
11 console.log(` Credits: ${plan.credits_per_month.toLocaleString('en-US')}/mo`);
12 console.log(` Parallel renders: ${plan.max_concurrent_requests}`);
13 console.log(` PSD templates: ${plan.psd_limit ?? "unlimited"}`);
14});
15
16// Looking up one specific plan: match the slug, never the index.
17const pro25k = plans.find(p => p.slug === "pro-25k");

Error Responses

500Internal Server Error
json
1{
2 "detail": "Failed to fetch subscription plans"
3}