Skip to content

Tutorial: Launch a Standalone Product

This tutorial walks through launching a standalone product on Kiket's API — from zero to first user signup. We'll use Správce GDPR (a GDPR request management tool) as the example.

Prerequisites

  • Access to Kiket admin panel (super admin)
  • A GitHub repository for your product definitions
  • A Stripe account (for billing)
  • A frontend app (Next.js, React, etc.)

Step 1: Register Your Product

In the Kiket admin panel, go to Platform > Products > New Product.

Field Example Value
Name Správce GDPR
Slug spravce-gdpr
Domain spravce-gdpr.cz
Default Locale cs (Czech)
Default Currency CZK
Status Active

CORS Allowed Origins:

https://spravce-gdpr.cz
http://localhost:3001

Branding (JSON):

{
  "product_name": "Správce GDPR",
  "from_email": "noreply@spravce-gdpr.cz",
  "support_email": "podpora@spravce-gdpr.cz",
  "logo_url": "https://spravce-gdpr.cz/logo.svg",
  "primary_color": "#1a73e8"
}

Feature Flags (JSON):

{
  "api_scopes": ["issues", "workflows", "intake_forms", "compliance_reports", "blockchain", "comments", "documents"],
  "max_users_per_org": 50,
  "ai_enabled": true,
  "document_workflows": true
}


Step 2: Create Your Definitions Repository

Create a GitHub repo (e.g., github.com/your-org/spravce-gdpr-definitions) with this structure:

.kiket/
├── project.yaml
├── issue_types.yaml
├── workflows/
│   └── gdpr-dsar.yaml
├── intakes/
│   └── gdpr-request.yaml
└── boards/
    └── dsar-board.yaml

See YAML Definition Reference for complete examples of each file.

  1. In the Kiket admin, go to Workflow Repositories > New
  2. Enter your GitHub repo URL, branch (main), and path (.kiket/workflows)
  3. Save — Kiket will sync definitions automatically
  4. Edit your Product Application and set the Workflow Repository to the one you just created

When new customers register, their organization automatically gets a copy of your definitions via the sync infrastructure.


Step 3: Set Up Stripe Connect

  1. Create a Stripe Connect Standard account for your product
  2. In the Kiket admin, edit your Product Application:
  3. Set Stripe Account ID (acct_...)
  4. Set Stripe Publishable Key (pk_...)
  5. Create products and prices in your Stripe Dashboard:
  6. Solo: 490 CZK/month
  7. Team: 990 CZK/month
  8. Agency: 3,490 CZK/month
  9. Add price IDs to your Feature Flags:
{
  "api_scopes": ["issues", "workflows", "intake_forms", "compliance_reports", "blockchain"],
  "pricing": {
    "solo": { "stripe_price_id": "price_..." },
    "team": { "stripe_price_id": "price_..." },
    "agency": { "stripe_price_id": "price_..." }
  }
}
  1. Configure Stripe webhook endpoint:
  2. URL: https://kiket.dev/api/v1/billing/stripe_webhook
  3. Events: customer.subscription.*, invoice.paid, invoice.payment_failed
  4. Set STRIPE_CONNECT_WEBHOOK_SECRET environment variable

Step 4: Build Your Frontend

Create a Next.js (or similar) app at spravce-gdpr.cz.

Auth integration

// lib/api.ts
const API_BASE = 'https://kiket.dev/api/v1';
const PRODUCT_SLUG = 'spravce-gdpr';

async function register(data: {
  email: string;
  password: string;
  password_confirmation: string;
  name: string;
  organization_name: string;
}) {
  const res = await fetch(`${API_BASE}/auth/register`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Product-Slug': PRODUCT_SLUG,
    },
    body: JSON.stringify(data),
  });
  return res.json();
  // Returns: { access_token, refresh_token, user, organization, subscription }
}

async function login(email: string, password: string) {
  const res = await fetch(`${API_BASE}/auth/login`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Product-Slug': PRODUCT_SLUG,
    },
    body: JSON.stringify({ email, password }),
  });
  const data = await res.json();

  // Handle multi-org case
  if (data.select_organization) {
    // Show org picker UI, then call /auth/login/select_org
    return { selectOrg: true, organizations: data.organizations };
  }

  return data; // { access_token, refresh_token, user, organization }
}

async function apiCall(path: string, token: string, options?: RequestInit) {
  const res = await fetch(`${API_BASE}${path}`, {
    ...options,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Product-Slug': PRODUCT_SLUG,
      'Accept-Language': 'cs',
      ...options?.headers,
    },
  });
  return res.json();
}

Token refresh

async function refreshTokens(refreshToken: string) {
  const res = await fetch(`${API_BASE}/auth/refresh`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ refresh_token: refreshToken }),
  });

  if (res.status === 403) {
    // Subscription expired — redirect to billing
    window.location.href = '/billing';
    return null;
  }

  return res.json(); // { access_token, refresh_token, expires_in }
}

Listing DSAR requests

// Fetch issues (DSARs) from the user's project
const dsars = await apiCall('/issues?issue_type=dsar&status=received,verification,processing', token);

// Transition a DSAR to the next state
await apiCall(`/issues/${issueId}/transition`, token, {
  method: 'POST',
  body: JSON.stringify({ transition: { state: 'verification' } }),
});

Billing integration

async function startCheckout(planTier: string) {
  const data = await apiCall('/billing/checkout_session', token, {
    method: 'POST',
    body: JSON.stringify({
      plan_tier: planTier,
      success_url: `${window.location.origin}/billing/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${window.location.origin}/billing`,
    }),
  });
  // Redirect to Stripe Checkout
  window.location.href = data.checkout_url;
}

Step 5: Deploy and Test

  1. Deploy your frontend to your domain
  2. Register a test account through your product's registration form
  3. Verify:
  4. Registration creates a user + organization + trial subscription
  5. Login returns a JWT scoped to your product
  6. Issues API returns data from your organization only
  7. Intake form is accessible at your configured URL
  8. Email subjects show your product name (not "Kiket")
  9. Billing checkout redirects to Stripe with your product branding

Step 6: Go Live Checklist

  • Product registered in Kiket admin with correct branding
  • Definitions repo synced (workflows, forms, issue types, boards)
  • Stripe Connect configured with prices for all plan tiers
  • CORS origins set for production domain
  • DNS configured for your product domain
  • Email sending configured (SPF/DKIM for your from_email domain)
  • Intake form tested (public submission → issue creation)
  • Workflow transitions tested (full lifecycle)
  • Billing flow tested (registration → trial → checkout → active)
  • Password reset flow tested (white-labeled email)
  • Error messages display in correct locale

Next Steps