> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wiseyield.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Portal Session

> Create a Dodo Payments billing portal session for managing subscription

## Endpoint

```
POST https://www.wiseyield.co/api/billing/create-portal-session
```

## Authentication

Requires a valid API key with `billing:write` scope.

## Request Body

No request body required.

## Response

Returns a Dodo Payments billing portal URL.

<ResponseField name="url" type="string">
  Dodo Payments billing portal URL to redirect user to
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://www.wiseyield.co/api/billing/create-portal-session \
    -H "Authorization: Bearer wy_YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://www.wiseyield.co/api/billing/create-portal-session',
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer wy_YOUR_API_KEY'
      }
    }
  );

  const { url } = await response.json();

  // Redirect user to Dodo Payments billing portal
  window.location.href = url;
  ```

  ```javascript JavaScript (With Error Handling) theme={null}
  async function openBillingPortal() {
    try {
      const response = await fetch('/api/billing/create-portal-session', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      if (!response.ok) {
        const error = await response.json();
        toast.error(error.error);
        return;
      }

      const { url } = await response.json();
      window.location.href = url;
    } catch (error) {
      toast.error('Failed to open billing portal');
    }
  }

  // Usage
  await openBillingPortal();
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      'https://www.wiseyield.co/api/billing/create-portal-session',
      headers={'Authorization': 'Bearer wy_YOUR_API_KEY'}
  )

  data = response.json()
  portal_url = data['url']

  # Redirect user to this URL
  print(f"Portal URL: {portal_url}")
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "url": "https://portal.dodopayments.com/session/ps_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "error": "Unauthorized"
  }
  ```

  ```json 404 Not Found theme={null}
  {
    "error": "No billing customer found"
  }
  ```

  ```json 500 Internal Server Error theme={null}
  {
    "error": "Failed to create portal session"
  }
  ```
</ResponseExample>

## Errors

<ResponseField name="401" type="error">
  Unauthorized - Missing or invalid API key
</ResponseField>

<ResponseField name="404" type="error">
  Not Found - User has no billing customer ID (never subscribed)
</ResponseField>

<ResponseField name="500" type="error">
  Internal Server Error - Failed to create Dodo Payments portal session
</ResponseField>

## How It Works

**Portal Flow**:

1. **API Call**: Client requests portal session
2. **Customer Lookup**: System finds user's Dodo Payments customer ID
3. **Session Creation**: Dodo Payments portal session created with return URL
4. **Redirect**: User redirected to Dodo Payments portal
5. **Self-Service**: User manages subscription on Dodo Payments
6. **Return**: User clicks "Return to..." and comes back to app

**Return URL**: `https://www.wiseyield.co/dashboard/billing`

**Customer Requirement**: User must have subscribed at least once. If no subscription history exists, returns 404 error.

## Customer Portal Features

**Dodo Payments portal allows users to**:

### Subscription Management

* Change subscription plan (upgrade/downgrade)
* Cancel subscription
* Resume canceled subscription
* View upcoming invoice

### Payment Methods

* Update credit card
* Add new payment method
* Set default payment method
* Remove old payment methods

### Billing History

* View all past invoices
* Download invoice PDFs
* See payment history

### Contact Information

* Update billing email
* Update billing address
* Update tax ID

## Use Cases

### Open Billing Portal

```javascript theme={null}
async function manageBilling() {
  const response = await fetch('/api/billing/create-portal-session', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  if (!response.ok) {
    if (response.status === 404) {
      toast.error('No subscription found. Subscribe first!');
      router.push('/pricing');
      return;
    }

    toast.error('Failed to open billing portal');
    return;
  }

  const { url } = await response.json();
  window.location.href = url;
}
```

### Manage Subscription Button

```javascript theme={null}
function ManageSubscriptionButton() {
  const [loading, setLoading] = useState(false);

  async function handleClick() {
    setLoading(true);
    try {
      const response = await fetch('/api/billing/create-portal-session', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      if (!response.ok) {
        toast.error('Unable to open billing portal');
        return;
      }

      const { url } = await response.json();
      window.location.href = url;
    } finally {
      setLoading(false);
    }
  }

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? 'Opening...' : 'Manage Subscription'}
    </button>
  );
}
```

### Check Before Opening Portal

```javascript theme={null}
async function openPortalIfEligible() {
  // First check if user has subscription
  const subResponse = await fetch('/api/billing/subscription', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { subscription } = await subResponse.json();

  if (!subscription) {
    toast.error('You need an active subscription to access the billing portal');
    router.push('/pricing');
    return;
  }

  // Open portal
  const portalResponse = await fetch('/api/billing/create-portal-session', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { url } = await portalResponse.json();
  window.location.href = url;
}
```

## Best Practices

<Tip>
  **Check Subscription First**: Verify user has subscription before showing "Manage Billing" button.
</Tip>

<Tip>
  **Loading State**: Show loading indicator while creating portal session.
</Tip>

<Tip>
  **Return URL Handling**: Portal returns to `/dashboard/billing`. Ensure this page shows updated subscription data.
</Tip>

<Tip>
  **New Tab Option**: Consider opening portal in new tab to prevent losing app state.
</Tip>

<Warning>
  **No Subscription = 404**: Users who never subscribed will get 404 error. Handle this gracefully.
</Warning>

<Warning>
  **Portal URL Expires**: Portal session URLs expire after a short time. Create new session each time.
</Warning>

<Warning>
  **Changes Apply Immediately**: Subscription changes in portal take effect immediately via webhooks.
</Warning>

## Integration Examples

### Billing Settings Page

```javascript theme={null}
function BillingSettingsPage() {
  const [subscription, setSubscription] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadSubscription() {
      const response = await fetch('/api/billing/subscription', {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      const { subscription } = await response.json();
      setSubscription(subscription);
      setLoading(false);
    }

    loadSubscription();
  }, []);

  async function openPortal() {
    const response = await fetch('/api/billing/create-portal-session', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${apiKey}` }
    });

    const { url } = await response.json();
    window.location.href = url;
  }

  if (loading) return <div>Loading...</div>;

  return (
    <div className="billing-page">
      <h1>Billing & Subscription</h1>

      {subscription ? (
        <>
          <div className="subscription-info">
            <h2>Current Plan: {subscription.plan}</h2>
            <p>Status: {subscription.status}</p>
            <p>Next billing: {new Date(subscription.currentPeriodEnd).toLocaleDateString()}</p>
          </div>

          <button onClick={openPortal} className="manage-button">
            Manage Subscription
          </button>

          <p className="help-text">
            Update payment method, change plan, or cancel subscription
          </p>
        </>
      ) : (
        <>
          <p>No active subscription</p>
          <a href="/pricing">View Plans</a>
        </>
      )}
    </div>
  );
}
```

### Portal Button with Confirmation

```javascript theme={null}
function BillingPortalButton({ actionType = 'manage' }) {
  const [opening, setOpening] = useState(false);

  async function handleClick() {
    const messages = {
      manage: 'Open billing portal to manage your subscription?',
      cancel: 'Open portal to cancel your subscription?',
      upgrade: 'Open portal to upgrade your plan?',
    };

    const confirmed = confirm(messages[actionType]);
    if (!confirmed) return;

    setOpening(true);
    try {
      const response = await fetch('/api/billing/create-portal-session', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      if (!response.ok) {
        toast.error('Unable to open portal');
        return;
      }

      const { url } = await response.json();
      window.location.href = url;
    } finally {
      setOpening(false);
    }
  }

  const buttonText = {
    manage: 'Manage Billing',
    cancel: 'Cancel Subscription',
    upgrade: 'Change Plan',
  };

  return (
    <button onClick={handleClick} disabled={opening}>
      {opening ? 'Opening...' : buttonText[actionType]}
    </button>
  );
}
```

### Portal Link in Dropdown Menu

```javascript theme={null}
function UserMenu() {
  const [subscription, setSubscription] = useState(null);

  useEffect(() => {
    async function loadSubscription() {
      const response = await fetch('/api/billing/subscription', {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      const { subscription } = await response.json();
      setSubscription(subscription);
    }

    loadSubscription();
  }, []);

  async function openPortal() {
    const response = await fetch('/api/billing/create-portal-session', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${apiKey}` }
    });

    const { url } = await response.json();
    window.location.href = url;
  }

  return (
    <DropdownMenu>
      <DropdownMenuItem onClick={() => router.push('/dashboard')}>
        Dashboard
      </DropdownMenuItem>

      <DropdownMenuItem onClick={() => router.push('/settings')}>
        Settings
      </DropdownMenuItem>

      {subscription && (
        <DropdownMenuItem onClick={openPortal}>
          Manage Billing
        </DropdownMenuItem>
      )}

      <DropdownMenuItem onClick={logout}>
        Sign Out
      </DropdownMenuItem>
    </DropdownMenu>
  );
}
```

### Portal in New Tab

```javascript theme={null}
async function openPortalInNewTab() {
  const response = await fetch('/api/billing/create-portal-session', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  if (!response.ok) {
    toast.error('Failed to open billing portal');
    return;
  }

  const { url } = await response.json();

  // Open in new tab instead of redirect
  window.open(url, '_blank');

  toast.success('Billing portal opened in new tab');
}
```

## Portal Return Handling

```javascript theme={null}
// In /dashboard/billing page
function BillingPage() {
  useEffect(() => {
    // Refresh subscription data when returning from portal
    async function refreshSubscription() {
      const response = await fetch('/api/billing/subscription', {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      const { subscription } = await response.json();

      // Update UI with latest subscription data
      setSubscription(subscription);

      // Show success message if recent change
      const recentlyUpdated = subscription &&
        (new Date() - new Date(subscription.updatedAt)) < 60000; // 1 minute

      if (recentlyUpdated) {
        toast.success('Subscription updated successfully');
      }
    }

    refreshSubscription();
  }, []);

  return <div>Billing Dashboard</div>;
}
```

## Related Endpoints

* [Get Subscription](/api-reference/billing/get-subscription) - GET current subscription status
* [Create Checkout Session](/api-reference/billing/create-checkout-session) - POST to start new subscription
* [Get Invoices](/api-reference/billing/get-invoices) - GET payment history
* [Get Usage](/api-reference/billing/get-usage) - GET current usage metrics
