> This is Payabli documentation. For a complete page index, fetch https://docs.payabli.com/llms.txt — append .md to any page URL for lightweight markdown. For section-level indexes, query parameters, and other AI-optimized access methods, see https://docs.payabli.com/ai-agents.md

# API-driven boarding quickstart

> Learn to use example code to set up a custom app for boarding using the Payabli API

This guide covers setting up a custom app for processing boarding applications using example code, including modifying the form layout, applying a custom theme, and changing individual form fields.
The result is a custom app that caters to private corporations with a fulfillment window of 30 days or less on all transactions and a minimum average transaction amount of \$100 USD.

Hosting your own boarding solution means that you're responsible for maintaining it.
Talk to your Payabli solutions engineer about whether API-driven boarding is a good option for your organization.

Visit the <a href="https://github.com/payabli/examples/tree/main/boarding">repository's README</a> for information about specific files, functions, component props, and other technical details.

## Dependencies

Before you begin, make sure you have the following installed on your machine:

* [npm](https://nodejs.org/en/download/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)

## Set up the app

Run these commands in your terminal to configure the boarding example app on your local machine:

```bash
git clone https://github.com/payabli/examples
```

```bash
cd examples/boarding
```

```bash
npm install
```

```bash
cp .env.template .env
```

Open the `.env` file in your code editor. Set `PAYABLI_API_TOKEN` to your Payabli API token and `BETTER_AUTH_SECRET` to any text string with a length of 32 characters:

```txt
  # your Payabli API token
  PAYABLI_API_TOKEN="123...XYZ"
  # which Payabli environment to use
  PAYABLI_ENVIRONMENT="sandbox"
  # your better-auth secret
  BETTER_AUTH_SECRET=ABCDEFGHIJKLMNOPQRSTUVWXYZ123456
  # base URL of your app
  BETTER_AUTH_URL=http://localhost:4321 # https://my-production-site.com
```

The `BETTER_AUTH_SECRET` is a string used to encrypt sensitive information.
You can generate a 32-character string using the [secret key generator](https://www.better-auth.com/docs/installation#set-environment-variables) in the better-auth documentation.
The `BETTER_AUTH_URL` is the base URL of your app.

Enter "yes" for all prompts:

```bash
npx @better-auth/cli generate
npx @better-auth/cli migrate
```

```bash
npm run dev
```

## Authentication

The example boarding app uses a JavaScript library called [better-auth](https://www.better-auth.com/) for user authentication.
Authentication is important because it prevents access to secure information by unauthorized users.

Navigate to `http://localhost:4321` in your web browser to see the login page.

<img alt="Screenshot of login page" src="https://files.buildwithfern.com/payabli.docs.buildwithfern.com/6d53bc55cdebdde5ba079c8bcd6582cc00c15f16716b75cee1e92335ea8a7ffb/images/boarding-quickstart-login.png" />

Click the "Sign up" link to create a new account.

<img alt="Screenshot of sign-up page" src="https://files.buildwithfern.com/payabli.docs.buildwithfern.com/ae4956ba620a666bb709e3d3cb606d5b203ff9b62bffef8834d8db073d2f9c95/images/boarding-quickstart-signup.png" />

After you sign up, you'll be authenticated and see the boarding form.

<img alt="Screenshot of boarding app" src="https://files.buildwithfern.com/payabli.docs.buildwithfern.com/b81ac5a697f4a8f145e1775de56905316c249ed609197769c11634eb2ff501f4/images/boarding-app-screenshot.png" />

## Customize the boarding form

The boarding form is built with React components that you can customize to fit your business needs.
Follow the walkthrough below to learn how to move form fields, change section details, add validation rules, and more:

+++ Modify form steps
++ Move the "Business Summary" field from the "Financial Information" section to the "Business Information." Then rename the section to "Corporation Overview".

/// Find the Financial Information section

The "Financial Information" section contains fields for financial statistics about a merchant.
It's located in the `src/components/PayabliForm.tsx` file.

```tsx
// src/components/PayabliForm.tsx
<WizardStep icon={<Banknote />} label="Financial Information">
    <h2 className="mb-4 w-full text-center text-2xl font-bold">
      Step 4: Financial Information
    </h2>
    <div className="items-end gap-4 md:grid md:grid-cols-2">
      <FormInput
        name="bsummary"
        label="Business Summary"
        tooltip="Brief description of your business activities"
      />
    // ...
</WizardStep>
```

/// Delete the `bsummary` field

Cut the `bsummary` field from the "Financial Information" section. You'll paste this later.

```tsx
// src/components/PayabliForm.tsx
<WizardStep icon={<Banknote />} label="Financial Information">
    <h2 className="mb-4 w-full text-center text-2xl font-bold">
      Step 4: Financial Information
    </h2>
    <div className="items-end gap-4 md:grid md:grid-cols-2">
    // ...
</WizardStep>
```

/// Find the Business Information section

The "Business Information" section contains fields for legal name and other business details.

```tsx
// src/components/PayabliForm.tsx
<WizardStep icon={<Building />} label="Business Information">
  <h2 className="mb-4 w-full text-center text-2xl font-bold">
    Step 1: Business Information
  </h2>
  <div className="items-end gap-4 md:grid md:grid-cols-2">
    <FormInput
      name="legalname"
      label="Legal Name"
      tooltip="The official registered name of your business"
    />
  // ...
</WizardStep>
```

/// Move business summary field

Add the business summary field to the "Business Information" section.

```tsx focus=12-16
// src/components/PayabliForm.tsx
<WizardStep icon={<Building />} label="Business Information">
  <h2 className="mb-4 w-full text-center text-2xl font-bold">
    Step 1: Business Information
  </h2>
  <div className="items-end gap-4 md:grid md:grid-cols-2">
    <FormInput
      name="legalname"
      label="Legal Name"
      tooltip="The official registered name of your business"
    />
    <FormInput
      name="bsummary"
      label="Business Summary"
      tooltip="Brief description of your business activities"
    />
  // ...
</WizardStep>
```

/// Change section label

Change the wizard step's label prop to say "Corporation Overview" instead of "Business Information".
Then change the icon prop to use the `Briefcase` icon provided by [lucide.dev](https://lucide.dev/).
Make sure to import the `Briefcase` icon at the top of the file.

```tsx focus=1-4
// src/components/PayabliForm.tsx
<WizardStep icon={<Briefcase />} label="Corporation Overview">
  <h2 className="mb-4 w-full text-center text-2xl font-bold">
    Step 1: Corporation Overview
  </h2>
  <div className="items-end gap-4 md:grid md:grid-cols-2">
    <FormInput
      name="legalname"
      label="Legal Name"
      tooltip="The official registered name of your business"
    />
    <FormInput
      name="bsummary"
      label="Business Summary"
      tooltip="Brief description of your business activities"
    />
  // ...
</WizardStep>
```

+++ Update form schema
++ Update the form schema to reflect the changes we made to the form fields.

/// Find the form schema

The schema defines validation rules and supports server-enforced prefills for form fields.
It's located in the `src/Schema.ts` file.
It uses the `createFormSchemaKit()` function from the `schemaPrefill` file to initialize the schema with custom validation rules.

```typescript
// src/Schema.ts
import { z } from 'zod';
import { createFormSchemaKit } from '../../lib/schemaPrefill';

const requireString = () => z.string().min(1, { message: 'This field is required' });
const requiredNumber = () => z.coerce.number().min(1, { message: 'This field is required' });

const formSchemaKit = createFormSchemaKit({
  // ... other fields
  legalname: requireString(),
  btype: requireString(),
  ticketamt: requiredNumber(),
  // ... other fields
});

export const formSchema = formSchemaKit.clientSchema;
export { formSchemaKit };
```

/// Add minimum validation

Add a minimum value validation for the ticket amount field.

```typescript focus=12-14
// src/Schema.ts
import { z } from 'zod';
import { createFormSchemaKit } from '../../lib/schemaPrefill';

const requireString = () => z.string().min(1, { message: 'This field is required' });
const requiredNumber = () => z.coerce.number().min(1, { message: 'This field is required' });

const formSchemaKit = createFormSchemaKit({
  // ... other fields
  legalname: requireString(),
  btype: requireString(),
  ticketamt: requiredNumber().min(100, { 
    message: 'Ticket amount must be at least $100' 
  }),
  // ... other fields
});

export const formSchema = formSchemaKit.clientSchema;
export { formSchemaKit };
```

/// Prefill and enforce business type

Wrap the `btype` field with the `prefill()` function to enforce a trusted value on the server before making calls to Payabli's API.
This is more secure than using Zod's `.default()` method, which only fills in a value when the input is missing or `undefined`.

```typescript focus=11
// src/Schema.ts
import { z } from 'zod';
import { createFormSchemaKit, prefill } from '../../lib/schemaPrefill';

const requireString = () => z.string().min(1, { message: 'This field is required' });
const requiredNumber = () => z.coerce.number().min(1, { message: 'This field is required' });

const formSchemaKit = createFormSchemaKit({
  // ... other fields
  legalname: requireString(),
  btype: prefill(requireString(), 'Private Corp'),
  ticketamt: requiredNumber().min(100, { 
    message: 'Ticket amount must be at least $100' 
  }),
  // ... other fields
});

export const formSchema = formSchemaKit.clientSchema;
export { formSchemaKit };
```

+++ Customize form fields
++ Customize the labels, tooltips, and options for specific form fields.

/// Find `ticketamt` field

Find the `<FormInput>` component for the ticket amount field.

```tsx
// src/components/PayabliForm.tsx
// ... other fields
<FormInput
  name="ticketamt"
  label="Ticket Amount"
  tooltip="Average amount per transaction"
  prefix="$"
  numeric
/>
// ... other fields
```

/// Specify minimum ticket amount

Customize the ticket amount field to show the minimum requirement.

```tsx focus=5-6
// src/components/PayabliForm.tsx
// ... other fields
<FormInput
  name="ticketamt"
  label="Ticket Amount ($100+)"
  tooltip="Average amount per transaction (min. $100)"
  prefix="$"
  numeric
/>
// ... other fields
```

/// Find `whenDelivered` field

Find the `<FormSelect>` component for the delivery timeframe field.

```tsx
// src/components/PayabliForm.tsx
// ... other fields
<FormSelect
  name="whenDelivered"
  label="When Delivered"
  options={[
    { value: '0-7 Days', label: '0-7 Days' },
    { value: '8-14 Days', label: '8-14 Days' },
    { value: '15-30 Days', label: '15-30 Days' },
    { value: 'Over 30 Days', label: 'Over 30 Days' },
  ]}
  tooltip="Typical timeframe for delivering products or services"
/>
// ... other fields
```

/// Remove "Over 30 Days" option

Remove the "Over 30 Days" option from the delivery timeframe field.

```tsx focus=7-9
// src/components/PayabliForm.tsx
// ... other fields
<FormSelect
  name="whenDelivered"
  label="When Delivered"
  options={[
    { value: '0-7 Days', label: '0-7 Days' },
    { value: '8-14 Days', label: '8-14 Days' },
    { value: '15-30 Days', label: '15-30 Days' },
  ]}
  tooltip="Typical timeframe for delivering products or services"
/>
// ... other fields
```

/// Find `whenRefunded` field

Find the `<FormSelect>` component for the refund timeframe field.

```tsx
// src/components/PayabliForm.tsx
// ... other fields
<FormSelect
  name="whenRefunded"
  label="When Refunded"
  options={[
    { value: 'Exchange Only', label: 'Exchange Only' },
    { value: 'No Refund or Exchange', label: 'No Refund or Exchange' },
    { value: '30 Days or Less', label: '30 Days or Less' },
    { value: 'Over 30 Days', label: 'Over 30 Days' },
  ]}
  tooltip="Typical timeframe for processing refunds"
/>
// ... other fields
```

/// Remove "Over 30 Days" refund option

Remove the "Over 30 Days" option from the refund timeframe field.

```tsx focus=7-9
// src/components/PayabliForm.tsx
// ... other fields 
<FormSelect
  name="whenRefunded"
  label="When Refunded"
  options={[
    { value: 'Exchange Only', label: 'Exchange Only' },
    { value: 'No Refund or Exchange', label: 'No Refund or Exchange' },
    { value: '30 Days or Less', label: '30 Days or Less' },
  ]}
  tooltip="Typical timeframe for processing refunds"
/>
// ... other fields
```

### Customize the app theme

The example app is built with [shadcn/ui](https://ui.shadcn.com/) and [Tailwind CSS](https://tailwindcss.com/).
You can customize the app's appearance by modifying the CSS file located at `styles/globals.css`.
The `:root` selector contains CSS variables that apply to light mode, and the `.dark` selector contains CSS variables for dark mode.

```css Default theme
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 240 10% 3.9%;
    --card: 0 0% 100%;
    --card-foreground: 240 10% 3.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 240 10% 3.9%;
    --primary: 240 5.9% 10%;
    --primary-foreground: 0 0% 98%;
    --secondary: 240 4.8% 95.9%;
    --secondary-foreground: 240 5.9% 10%;
    --muted: 240 4.8% 95.9%;
    --muted-foreground: 240 3.8% 46.1%;
    --accent: 240 4.8% 95.9%;
    --accent-foreground: 240 5.9% 10%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 0 0% 98%;
    --border: 240 5.9% 90%;
    --input: 240 5.9% 90%;
    --ring: 240 5.9% 10%;
    --radius: 0.5rem;
    --chart-1: 12 76% 61%;
    --chart-2: 173 58% 39%;
    --chart-3: 197 37% 24%;
    --chart-4: 43 74% 66%;
    --chart-5: 27 87% 67%;
  }

  .dark {
    --background: 240 10% 3.9%;
    --foreground: 0 0% 98%;
    --card: 240 10% 3.9%;
    --card-foreground: 0 0% 98%;
    --popover: 240 10% 3.9%;
    --popover-foreground: 0 0% 98%;
    --primary: 0 0% 98%;
    --primary-foreground: 240 5.9% 10%;
    --secondary: 240 3.7% 15.9%;
    --secondary-foreground: 0 0% 98%;
    --muted: 240 3.7% 15.9%;
    --muted-foreground: 240 5% 64.9%;
    --accent: 240 3.7% 15.9%;
    --accent-foreground: 0 0% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 0 0% 98%;
    --border: 240 3.7% 15.9%;
    --input: 240 3.7% 15.9%;
    --ring: 240 4.9% 83.9%;
    --chart-1: 220 70% 50%;
    --chart-2: 160 60% 45%;
    --chart-3: 30 80% 55%;
    --chart-4: 280 65% 60%;
    --chart-5: 340 75% 55%;
  }
}

@layer base {
  * {
    @apply border-border;
  }

  html {
    @apply scroll-smooth;
  }

  body {
    @apply bg-background text-foreground;
    font-synthesis-weight: none;
    text-rendering: optimizeLegibility;
  }
}

@layer base {
    img {
        display: initial;
    }
}
```

### Additional customization

To learn more about the available props for the form components and additional customization options, see the following resources:

* **Form fields**: See the example app's [README](https://github.com/payabli/examples/tree/main/boarding) for available props
* **Icons**: Browse available icons at [lucide.dev](https://lucide.dev/)

## Related resources

See these related resources to help you get the most out of Payabli.

* **[Manage notifications](/guides/pay-ops-developer-notifications-manage)** - Set up notifications to stay informed about boarding application status changes

- **[Boarding API example](https://github.com/payabli/examples/tree/main/boarding)** - Full API-driven boarding application example on GitHub
- **[Boarding statuses reference](/guides/pay-ops-boarding-status-reference)** - Learn about statuses and substatuses during the merchant boarding process
- **[Paypoint statuses](/guides/pay-ops-paypoint-status-reference)** - Learn about the different statuses of paypoints

* **[Send prefilled boarding applications](/guides/pay-ops-developer-boarding-send-prefilled-apps)** - Learn how to send prefilled boarding applications to prospective merchants