Visit APIToGo on LinkedIn
  • Documentation
  • Components
  • Themes
Quickstart
CLI
MCP
Configuration
Look and feel
    Colors & ThemeFont & TypographyLanding PageFooter
Writing
Guides
Deployment
powered by apitogo
Look and feel

Landing Page

The portal's home page is a React file: apitogo/portal/landing/LandingPage.tsx, named by portal.landing in apitogo.config.json. apitogo add portal writes a starter one (or apitogo create --template full). It composes section components from @lukoweb/apitogo-module-landing inside a LandingShell:

ComponentPurpose
HeroSectionHeadline, description, CTAs and code examples
TrustSectionLogos or social proof
FeaturesSectionFeature grid
StepsSectionNumbered "how it works" steps
ApiShowcaseSectionGrid or method + path list of your API
PlansPreviewSectionShort plan comparison linking to /pricing
PricingCtaSectionClosing call to action

Edit the file directly to add, remove or reorder sections. Remove portal.landing from the config to have no home page: / then opens the first docs page. The footer is a file of its own (see Footer).

API showcase

ApiShowcaseSection renders the items you pass it. The scaffold ships with a static sample list:

Code
<ApiShowcaseSection apiShowcase={{ title: "Sample endpoints", layout: "list", // or "grid" browseAllHref: "/api", items: [ { name: "List users", path: "/v1/users", method: "GET", summary: "List paginated users" }, ], }} />

Each item accepts name, path, and optionally method, summary, icon and latency.

Live APIs

Instead of maintaining the list by hand, you can load your API's real operations. Your dev portal backend syncs the gateway actions of your project (every operation discovered from the OpenAPI spec you deployed) and serves them publicly at GET /api/v1/actions. The useGatewayActions() hook fetches that list; you decide what to show and pass the result to ApiShowcaseSection.

Code
import { ApiShowcaseSection, LandingShell, toApiShowcaseItems, useGatewayActions, } from "@lukoweb/apitogo-module-landing"; const staticItems = [ { name: "List users", path: "/v1/users", method: "GET", summary: "List paginated users" }, ]; // Optional friendly labels, keyed by action key (`method:path`, lower-case method). const labels: Record<string, string> = { "get:/v1/users": "List paginated users", "post:/v1/orders": "Create an order", }; export default function LandingPage() { const { data } = useGatewayActions({ inPlan: true }); const liveItems = toApiShowcaseItems( (data?.actions ?? []).filter((action) => action.path.startsWith("/v1/")).slice(0, 6), labels, ); return ( <LandingShell title="My API"> <ApiShowcaseSection apiShowcase={{ title: "Our APIs", layout: "list", browseAllHref: "/api", items: liveItems.length > 0 ? liveItems : staticItems, }} /> </LandingShell> ); }

Filters

useGatewayActions() accepts server-side filters. All filters are combined:

OptionTypeDescription
inPlanbooleanOnly actions included in at least one active plan
planstringOnly actions included in the plan with this SKU
isPublicbooleanOnly actions callable without an API key (or only not)
methodstringHTTP method, e.g. GET
pathPrefixstringPath prefix, e.g. /v1/orders
limitnumberMaximum number of actions (server cap: 500)
enabledbooleanSet to false to skip the request

Anything else — sorting, renaming, grouping, picking a hand-curated subset — is up to you in plain TypeScript before you build the items.

Each action has this shape:

Code
type GatewayAction = { actionKey: string; // e.g. "get:/orders/{id}" method: string; // e.g. "GET" path: string; // e.g. "/orders/{id}" isPublic: boolean; // callable without an API key };

Actions only carry the method and path. Use the summaries argument of toApiShowcaseItems() (or your own mapping) to add human-friendly names.

Always keep a fallback

The hook runs in the browser only, so the prerendered HTML never contains a stale list. data is undefined:

  • while the request is loading,
  • if the request fails,
  • in preview and demo mode, or when no dev portal backend URL is configured (for example in npm run dev before your portal is provisioned).

ApiShowcaseSection renders nothing for an empty list, so pass a static fallback as shown above.

What becomes public

GET /api/v1/actions is anonymous: every operation in the OpenAPI spec deployed to your gateway is listable, including ones you may not document in the portal. Use inPlan: true to limit the list to operations your plans actually sell, and remove internal operations from the spec you deploy to the gateway if they must not be discoverable.

Freshness and limits

  • The backend syncs actions at startup and on the plan sync interval (15 minutes by default). A new release shows up after the next sync.
  • Responses are cacheable for 5 minutes (Cache-Control: public, max-age=300).
  • The endpoint is rate limited per client IP (60 requests per minute).

Troubleshooting

  • The list is always the fallback: the project has no plans backend yet. Add plans to the config and run apitogo deploy.
  • CORS errors: the backend only allows the portal's configured frontend URL. Serving the site from another domain requires adding it to the backend's allowed origins.
  • Empty list on a new portal: the portal is not activated yet, or the gateway has no release. Deploy your backend, then wait for the next sync.

Plans preview

PlansPreviewSection shows the same plans as /pricing. The scaffold loads them with the usePlansPreview hook from the billing plugin, so you only maintain plans in one place, apitogo/plans.json:

Code
import { usePlansPreview } from "@lukoweb/apitogo-plugin-dev-portal-billing"; export default function LandingPage() { const plans = usePlansPreview(); return ( <LandingShell title="…"> {/* … */} <PlansPreviewSection plans={plans} /> </LandingShell> ); }

In a live portal the plans come from your dev portal backend (GET /api/v1/plans); in local preview, and while the live catalog loads, they come from apitogo/plans.json. Each card shows the plan's displayName, price and billing period, and its first two feature lines (features, or lines derived from limits). The highlighted card matches the "Popular" plan on /pricing.

To write the cards by hand instead, pass a static array of { name, price, unit, blurb, featured } objects to plans.

Last modified on November 10, 2026
Font & TypographyFooter
On this page
  • API showcase
  • Live APIs
    • Filters
    • Always keep a fallback
    • What becomes public
    • Freshness and limits
    • Troubleshooting
  • Plans preview
React
React
TypeScript
React