Initial copy from influencer-biz

This commit is contained in:
Jakob Wennberg
2026-02-13 14:00:46 +01:00
commit a25d10a528
391 changed files with 96224 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
# Product Requirements Document: Influencer Business-in-a-Box MVP
## Executive Summary
A financial management system for Swedish influencers and content creators operating as sole proprietors (enskild firma). The core value proposition: eliminate tax anxiety and administrative burden through automated compliance.
## Target User
- Swedish content creators operating as:
- Enskild näringsidkare (sole proprietor)
- Aktiebolag (limited company)
- Income from multiple sources: brand deals, affiliate
- Revenue range: 50k-2M SEK/year
- Pain: Fear of Skatteverket, hatred of admin, confusion about VAT on international payments
## Entity Type Differences
| Aspect | Enskild Firma | Aktiebolag |
|--------|---------------|------------|
| Tax on profit | Egenavgifter (28.97%) + kommunalskatt (~32%) | Bolagsskatt (20.6%) |
| Owner payment | Eget uttag (withdrawal) | Lön + utdelning |
| Annual filing | NE-bilaga | Årsredovisning + INK2 |
| VAT | Same rules | Same rules |
| Personal liability | Yes | No (limited) |
MVP focuses on shared features (invoicing, VAT, transactions). Entity-specific tax calculations adapt based on company_settings.entity_type.
## MVP Scope
### In Scope (Phase 1)
1. **Real-time Tax Dashboard** ("Disponibelt Saldo")
- Show available balance after estimated tax, employer contributions (egenavgifter), and VAT
- Visual "locked" vs "spendable" money display
- Adapt calculations based on entity type (enskild firma vs AB)
- Compare against user's debiterad preliminärskatt (F-skatt level) to warn about under/overpayment
2. **Bank Integration via Enable Banking (PSD2)**
- Connect multiple bank accounts
- Real-time transaction sync
- Consent renewal handling (90-180 days)
3. **Transaction Categorization**
- Swipe interface: Business / Private / Unsure
- Expense categories with schablonavdrag suggestions
- Smart defaults: hemmakontor 2000 kr/year, bil 18.50 kr/mil
4. **Invoice Generation**
- Create professional PDF invoices
- Multi-currency support (SEK, EUR, USD)
- Auto-convert foreign currency to SEK using Riksbanken daily rates
- Automatic VAT logic:
- Swedish customer: 25% VAT
- EU business (with VAT number): Reverse charge
- Non-EU: No VAT
- Customizable starting invoice number
- Credit notes (kreditfaktura) for corrections
5. **Income Overview**
- Aggregate invoiced revenue + platform payouts
- Track against VAT registration threshold (80k SEK)
- Selectable momsperiod (monthly/quarterly)
### Out of Scope (Future Phases)
- Receipt photo capture and matching
- Benefits/gifts tracking (förmånshantering)
- NE-bilaga / Årsredovisning auto-generation
- Automated payment reminders
- Quotes/proforma invoices
- English UI
## User Stories
### Onboarding (Required before feature access)
- As a new user, I complete a guided setup wizard before accessing the app
- As a user, I select my entity type (enskild firma or aktiebolag)
- As a user, I enter my company details (name, org.nr, address)
- As a user, I confirm my F-skatt registration status
- As a user, I set my VAT registration status and momsperiod
- As a user, I enter my preliminary tax (debiterad F-skatt) amount
- As a user, I add my bank details for invoice payments
- As a user, I connect at least one bank account via Enable Banking
- As a user, I cannot skip required fields - all must be completed
### Dashboard
- As a user, I see my total balance, locked tax amount, and spendable amount immediately on login
- As a user, I see a warning when approaching VAT registration threshold
### Transactions
- As a user, I connect my bank account securely
- As a user, I categorize transactions by swiping
- As a user, I can edit categorization later
- As a user, I see uncategorized transaction count as a badge
### Invoicing
- As a user, I create an invoice in under 60 seconds
- As a user, the system auto-detects if reverse charge applies
- As a user, I download PDF or copy link to hosted invoice
- As a user, I see invoice status: draft, sent, paid, overdue
### Settings
- As a user, I enter my company details once (org.nr, F-skatt, address)
- As a user, I save customer profiles for repeat invoicing
## Success Metrics
- Time to first invoice: <5 minutes from signup
- Transaction categorization rate: >80% within 7 days
- User retention at 30 days: >40%
## Constraints
- Must comply with Swedish Bokföringslagen
- Bank integration must use licensed PSD2 provider (TSP model via Tink/Nordigen)
- Invoice PDF must contain all legally required fields
- All financial calculations are estimates, not tax advice (disclaimer required)
- 7-year retention of all räkenskapsinformation per Bokföringslagen
- Non-custodial design: app never holds user funds
- GDPR compliant with EU/EES data residency
## Regulatory Notes for MVP
### PSD2 Strategy
Use Technical Service Provider (TSP) model. Nordigen offers free AISP tier. Avoids 125k EUR capital requirement and FI licensing process.
### Non-Custodial Principle
Critical: System must never hold client funds. For tax savings feature, use PISP to move funds to user's own separate bank account, not an app-controlled wallet. This avoids Lagen om redovisningsmedel requirements.
### Data Retention
- Bokföringslagen requires 7-year retention of verifikationer
- GDPR "right to be forgotten" does not override this legal obligation
- Technical lock must prevent deletion of accounting records even on account closure
- Non-accounting data (support chats, preferences) can be deleted on request
### Digital Archiving (July 2024 Law)
Receipt photos must be stored in immutable format (PDF/A or locked JPEG). Paper originals no longer required if digital capture is tamper-proof.
+343
View File
@@ -0,0 +1,343 @@
# Technical Architecture Document
## Stack Overview
```
Frontend: Next.js (App Router) + React + TypeScript
Styling: Tailwind CSS + shadcn/ui
Backend: Next.js API Routes + Supabase Edge Functions
Database: Supabase (PostgreSQL)
Auth: Supabase Auth (magic link + Google OAuth)
Storage: Supabase Storage (invoice PDFs)
Banking: Enable Banking (PSD2 AISP)
PDF Gen: @react-pdf/renderer or Puppeteer
Hosting: Vercel
Currency: Riksbanken API for exchange rates
```
## Project Structure
```
/
├── app/
│ ├── (auth)/
│ │ ├── login/page.tsx
│ │ └── callback/page.tsx
│ ├── (onboarding)/
│ │ └── onboarding/
│ │ ├── page.tsx # Wizard container
│ │ ├── step-1/page.tsx # Entity type
│ │ ├── step-2/page.tsx # Company details
│ │ ├── step-3/page.tsx # Tax registration
│ │ ├── step-4/page.tsx # Preliminary tax
│ │ ├── step-5/page.tsx # Bank details for invoices
│ │ └── step-6/page.tsx # Connect bank (Enable Banking)
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ ├── page.tsx # Main dashboard
│ │ ├── transactions/
│ │ │ └── page.tsx # Swipe categorization
│ │ ├── invoices/
│ │ │ ├── page.tsx # Invoice list
│ │ │ ├── new/page.tsx # Create invoice
│ │ │ ├── [id]/page.tsx # Invoice detail
│ │ │ └── [id]/credit/page.tsx # Create credit note
│ │ ├── customers/
│ │ │ └── page.tsx # Customer management
│ │ └── settings/
│ │ └── page.tsx # Company profile
│ └── api/
│ ├── banking/
│ │ ├── connect/route.ts # Initiate Enable Banking connection
│ │ ├── callback/route.ts # PSD2 callback
│ │ └── sync/route.ts # Fetch transactions
│ ├── invoices/
│ │ ├── route.ts # CRUD
│ │ ├── [id]/pdf/route.ts # Generate PDF
│ │ └── [id]/credit/route.ts # Create credit note
│ ├── currency/
│ │ └── rates/route.ts # Riksbanken exchange rates
│ └── webhooks/
│ └── banking/route.ts # Transaction webhooks
├── components/
│ ├── ui/ # shadcn components
│ ├── onboarding/
│ │ ├── WizardProgress.tsx
│ │ ├── EntityTypeSelector.tsx
│ │ └── BankConnectButton.tsx
│ ├── dashboard/
│ │ ├── BalanceCard.tsx
│ │ ├── TaxBreakdown.tsx
│ │ ├── FskattWarning.tsx
│ │ └── RevenueChart.tsx
│ ├── transactions/
│ │ ├── SwipeCard.tsx
│ │ └── TransactionList.tsx
│ └── invoices/
│ ├── InvoiceForm.tsx
│ ├── InvoicePreview.tsx
│ ├── CurrencySelector.tsx
│ └── CustomerSelect.tsx
├── lib/
│ ├── supabase/
│ │ ├── client.ts # Browser client
│ │ ├── server.ts # Server client
│ │ └── admin.ts # Service role client
│ ├── banking/
│ │ └── enable-banking.ts # Enable Banking API wrapper
│ ├── currency/
│ │ └── riksbanken.ts # Exchange rate fetching
│ ├── tax/
│ │ ├── calculator.ts # Swedish tax calculations
│ │ ├── expense-warnings.ts # Non-deductible expense alerts
│ │ ├── schablonavdrag.ts # Standard deductions
│ │ └── fskatt-warning.ts # Preliminary tax comparison
│ ├── invoice/
│ │ ├── vat-rules.ts # VAT logic
│ │ └── pdf-generator.ts
│ └── utils/
│ └── currency.ts
├── types/
│ └── index.ts # TypeScript interfaces
└── supabase/
├── migrations/ # Database migrations
└── seed.sql # Test data
```
## Authentication Flow
1. User lands on `/login`
2. Enters email → Supabase sends magic link
3. User clicks link → redirected to `/callback`
4. Callback exchanges code for session
5. **Onboarding check:** Query `company_settings` for user
- If no settings exist → redirect to `/onboarding`
- If settings exist but `onboarding_complete = false` → redirect to `/onboarding`
- If settings complete → redirect to `/` (dashboard)
Alternative: Google OAuth for faster onboarding (still requires completing wizard).
## Onboarding Flow
```
1. User completes auth
2. Middleware checks company_settings.onboarding_complete
3. If incomplete → force redirect to /onboarding
4. Wizard steps (cannot skip):
a. Entity type selection
b. Company details (name, org.nr, address)
c. Tax registration (F-skatt, VAT, momsperiod)
d. Preliminary tax amount (optional but encouraged)
e. Bank details for invoices
f. Connect bank via Enable Banking (required)
5. On completion → set onboarding_complete = true
6. Redirect to dashboard
```
**Middleware Logic:**
```typescript
// middleware.ts - simplified
const publicRoutes = ['/login', '/auth/callback']
const onboardingRoutes = ['/onboarding']
if (!user) {
// Not logged in → login page
return redirect('/login')
}
const settings = await getCompanySettings(user.id)
if (!settings?.onboarding_complete) {
// Onboarding incomplete → force wizard
if (!onboardingRoutes.some(r => path.startsWith(r))) {
return redirect('/onboarding')
}
}
// Onboarding complete → allow access
```
## Bank Connection Flow
### PSD2 Provider: Enable Banking
Using Enable Banking as the AISP provider.
**Why Enable Banking:**
- Good coverage of Swedish banks (Nordea, SEB, Handelsbanken, Swedbank, etc.)
- Reasonable pricing for startups
- TSP model: they hold the license, you process data
- REST API with good documentation
**Consent Lifecycle:**
- PSD2 RTS allows 90-180 day consent periods (bank dependent)
- System must track consent expiry per bank connection
- Proactive notification: "Din bankkoppling till Nordea löper ut om 7 dagar"
**Multiple Accounts:**
- Users can connect multiple bank accounts (business + savings)
- Each connection tracked separately with own consent expiry
- Dashboard aggregates across all connected accounts
```
1. User clicks "Koppla bank"
2. Frontend calls POST /api/banking/connect
3. Backend creates Enable Banking session, returns redirect URL
4. User selects bank, authenticates with BankID (SCA requirement)
5. Enable Banking redirects to /api/banking/callback
6. Backend stores connection metadata (consent expiry, account info)
7. Initial transaction fetch (90 days history typical)
8. User redirected to /transactions
9. Webhook receives new transactions in real-time
10. At consent expiry - 7 days: prompt re-consent flow
```
### Non-Custodial Design (Critical)
The app must NEVER hold user funds. This avoids:
- Lagen om redovisningsmedel (1944:181)
- Klientmedelskonto requirements
- Full betalningsinstitut licensing
For future "tax savings" feature:
- Use PISP to instruct user's bank to transfer to user's OWN savings account
- App orchestrates, never touches money
- User maintains full control and ownership
## Currency Conversion
### Riksbanken Integration
For multi-currency invoicing, convert foreign amounts to SEK for bokföring.
```typescript
// lib/currency/riksbanken.ts
interface ExchangeRate {
currency: string
rate: number
date: string
}
const RIKSBANKEN_API = 'https://api.riksbank.se/swea/v1/CrossRates'
export async function getExchangeRate(
currency: 'EUR' | 'USD' | 'GBP',
date: string // YYYY-MM-DD
): Promise<number> {
// Riksbanken publishes daily rates
// Use booking date for invoice conversion
const response = await fetch(
`${RIKSBANKEN_API}/${currency}/SEK/${date}`
)
const data = await response.json()
return data.value
}
// Invoice creation: store both original and SEK amounts
interface InvoiceAmounts {
originalCurrency: string
originalTotal: number
exchangeRate: number
exchangeRateDate: string
sekTotal: number // This is what goes in bokföring
}
```
**Bokföringslagen requirement:** Foreign currency transactions must be converted to SEK. The exchange rate on the invoice date (fakturadatum) is typically used.
## Tax Calculation Logic
Swedish sole proprietor (enskild näringsidkare) tax components:
```typescript
interface TaxBreakdown {
grossIncome: number; // Total invoiced + received
deductibleExpenses: number; // Categorized business expenses
netIncome: number; // grossIncome - deductibleExpenses
// Egenavgifter (self-employment contributions) ~28.97%
egenavgifter: number;
// Preliminary income tax (kommunalskatt ~32% avg)
incomeTax: number;
// VAT collected (if registered)
vatCollected: number;
vatDeductible: number;
vatPayable: number;
// Total locked
totalLocked: number;
// Available to spend
disponibelt: number;
}
```
**Important**: These are estimates. Display clear disclaimer.
## VAT Rules Engine
```typescript
type VatScenario =
| 'swedish_customer' // 25% VAT
| 'eu_business_valid_vat' // 0% + reverse charge text
| 'eu_consumer' // 25% VAT (or destination country rate for digital services)
| 'non_eu' // 0% VAT, export
interface VatDecision {
rate: number;
reverseCharge: boolean;
invoiceText: string | null; // Legal text to include
}
```
Validation: EU VAT numbers validated via VIES API.
## Security Considerations
- Row Level Security (RLS) on all tables
- User can only access own data
- Banking tokens encrypted at rest (Supabase Vault or env vars)
- Invoice PDFs in private bucket, signed URLs for access
- No PII in logs
- Rate limiting on API routes
## Environment Variables
```
# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# Banking (Tink example)
TINK_CLIENT_ID=
TINK_CLIENT_SECRET=
TINK_REDIRECT_URI=
# App
NEXT_PUBLIC_APP_URL=https://app.yourdomain.se
# PDF
PDF_STORAGE_BUCKET=invoices
```
## Deployment
Vercel configuration:
```json
// vercel.json
{
"crons": [
{
"path": "/api/banking/sync",
"schedule": "0 6 * * *"
}
]
}
```
Database migrations run via Supabase CLI or dashboard.
+688
View File
@@ -0,0 +1,688 @@
# Database Schema
## Entity Relationship Diagram
```
users (Supabase Auth)
│
├── profiles (1:1)
│ └── company_settings (1:1)
│
├── bank_connections (1:N)
│ └── transactions (1:N)
│
├── customers (1:N)
│
└── invoices (1:N)
└── invoice_items (1:N)
```
## Tables
### profiles
Extends Supabase auth.users.
```sql
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT NOT NULL,
full_name TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);
```
### company_settings
Business/tax information for the company (enskild firma or AB).
```sql
CREATE TYPE entity_type AS ENUM (
'enskild_firma',
'aktiebolag'
);
CREATE TYPE moms_period AS ENUM (
'monthly',
'quarterly'
);
CREATE TABLE company_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
-- Onboarding status
onboarding_complete BOOLEAN DEFAULT FALSE,
onboarding_step INTEGER DEFAULT 1, -- Track progress (1-6)
-- Entity type
entity_type entity_type NOT NULL DEFAULT 'enskild_firma',
-- Company info
company_name TEXT NOT NULL,
org_number TEXT, -- Organisationsnummer (required for AB)
vat_number TEXT, -- SE + org_number + 01 (if VAT registered)
f_skatt_registered BOOLEAN DEFAULT TRUE,
-- Address
address_line1 TEXT,
address_line2 TEXT,
postal_code TEXT,
city TEXT,
country TEXT DEFAULT 'SE',
-- Banking
bank_name TEXT,
clearing_number TEXT,
account_number TEXT,
iban TEXT,
bic TEXT,
-- Contact
phone TEXT,
website TEXT,
-- Tax settings
municipal_tax_rate DECIMAL(5,2) DEFAULT 32.00, -- User's kommun (enskild firma)
preliminary_tax_monthly DECIMAL(12,2), -- Debiterad F-skatt per month
vat_registered BOOLEAN DEFAULT FALSE,
vat_registration_date DATE,
moms_period moms_period DEFAULT 'quarterly',
-- Fiscal year (räkenskapsår)
fiscal_year_start_month INTEGER DEFAULT 1, -- 1 = calendar year, other = brutet räkenskapsår
-- Example: 7 = July start (July 1 - June 30)
-- Invoice settings
invoice_prefix TEXT DEFAULT 'INV',
next_invoice_number INTEGER DEFAULT 1001, -- Allow unusual starting numbers
payment_terms_days INTEGER DEFAULT 30,
default_currency TEXT DEFAULT 'SEK',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id)
);
-- RLS
ALTER TABLE company_settings ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own company settings"
ON company_settings FOR ALL
USING (auth.uid() = user_id);
```
### bank_connections
PSD2 bank connection records.
```sql
CREATE TABLE bank_connections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
provider TEXT NOT NULL, -- 'tink', 'enablebanking'
provider_connection_id TEXT, -- External ID
bank_name TEXT,
account_name TEXT,
account_number_masked TEXT, -- Last 4 digits
status TEXT DEFAULT 'pending', -- pending, active, expired, error
consent_expires_at TIMESTAMPTZ,
last_synced_at TIMESTAMPTZ,
-- Encrypted tokens stored in Supabase Vault or as encrypted column
access_token_encrypted TEXT,
refresh_token_encrypted TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS
ALTER TABLE bank_connections ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own bank connections"
ON bank_connections FOR ALL
USING (auth.uid() = user_id);
```
### transactions
Bank transactions imported via PSD2.
```sql
CREATE TYPE transaction_category AS ENUM (
'uncategorized',
'business_income',
'business_expense',
'private',
'tax_payment',
'vat_payment',
'transfer'
);
CREATE TYPE expense_type AS ENUM (
'equipment',
'software',
'travel',
'meals',
'office',
'marketing',
'professional_services',
'other'
);
CREATE TABLE transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
bank_connection_id UUID REFERENCES bank_connections(id) ON DELETE SET NULL,
-- Transaction data from bank
external_id TEXT, -- Bank's transaction ID
booking_date DATE NOT NULL,
value_date DATE,
amount DECIMAL(12,2) NOT NULL, -- Negative = expense, Positive = income
currency TEXT DEFAULT 'SEK',
description TEXT,
counterparty_name TEXT,
counterparty_account TEXT,
mcc_code TEXT, -- Merchant Category Code from bank
-- Categorization
category transaction_category DEFAULT 'uncategorized',
expense_type expense_type,
business_percentage INTEGER DEFAULT 100, -- For mixed-use items
-- BAS Accounting
bas_account TEXT, -- e.g., '5410', '2013', '5910'
bas_account_auto BOOLEAN DEFAULT FALSE, -- Was this auto-categorized?
bas_account_confidence TEXT, -- 'high', 'medium', 'low'
reverse_charge_applied BOOLEAN DEFAULT FALSE,
-- VAT tracking
vat_amount DECIMAL(12,2), -- Calculated or from receipt
vat_rate DECIMAL(5,2), -- 25, 12, 6, or 0
-- Metadata
notes TEXT,
receipt_url TEXT, -- Link to uploaded receipt
linked_invoice_id UUID, -- If this is payment for an invoice
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, external_id)
);
-- Indexes
CREATE INDEX idx_transactions_user_date ON transactions(user_id, booking_date DESC);
CREATE INDEX idx_transactions_category ON transactions(user_id, category);
-- RLS
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own transactions"
ON transactions FOR ALL
USING (auth.uid() = user_id);
```
### customers
Invoice recipients.
```sql
CREATE TYPE customer_type AS ENUM (
'individual',
'swedish_business',
'eu_business',
'non_eu_business'
);
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
customer_type customer_type NOT NULL,
-- Basic info
name TEXT NOT NULL,
email TEXT,
phone TEXT,
-- Business info
org_number TEXT,
vat_number TEXT, -- For EU reverse charge validation
vat_number_validated BOOLEAN DEFAULT FALSE,
vat_number_validated_at TIMESTAMPTZ,
-- Address
address_line1 TEXT,
address_line2 TEXT,
postal_code TEXT,
city TEXT,
country TEXT NOT NULL DEFAULT 'SE',
-- Defaults for invoicing
default_payment_terms_days INTEGER,
default_currency TEXT DEFAULT 'SEK',
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS
ALTER TABLE customers ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own customers"
ON customers FOR ALL
USING (auth.uid() = user_id);
```
### invoices
```sql
CREATE TYPE invoice_status AS ENUM (
'draft',
'sent',
'viewed',
'paid',
'overdue',
'cancelled'
);
CREATE TYPE invoice_type AS ENUM (
'invoice',
'credit_note'
);
CREATE TYPE vat_treatment AS ENUM (
'standard', -- Normal VAT rate
'reverse_charge', -- EU B2B
'export', -- Non-EU
'exempt' -- VAT exempt services
);
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
customer_id UUID NOT NULL REFERENCES customers(id),
-- Invoice identification
invoice_number TEXT NOT NULL,
invoice_type invoice_type NOT NULL DEFAULT 'invoice',
credited_invoice_id UUID REFERENCES invoices(id), -- For credit notes: which invoice is credited
reference TEXT, -- Customer's reference/PO number
-- Dates
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE NOT NULL,
-- Original currency (what customer pays)
currency TEXT NOT NULL DEFAULT 'SEK',
subtotal DECIMAL(12,2) NOT NULL DEFAULT 0,
vat_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
total DECIMAL(12,2) NOT NULL DEFAULT 0,
-- SEK conversion (for bokföring)
exchange_rate DECIMAL(10,6) DEFAULT 1.0,
exchange_rate_date DATE,
subtotal_sek DECIMAL(12,2) NOT NULL DEFAULT 0,
vat_amount_sek DECIMAL(12,2) NOT NULL DEFAULT 0,
total_sek DECIMAL(12,2) NOT NULL DEFAULT 0,
-- VAT handling
vat_treatment vat_treatment NOT NULL DEFAULT 'standard',
vat_rate DECIMAL(5,2) DEFAULT 25.00,
reverse_charge_text TEXT, -- Legal text for reverse charge
moms_ruta INTEGER, -- Which ruta in momsdeklaration (5, 39, 40)
-- Status
status invoice_status DEFAULT 'draft',
sent_at TIMESTAMPTZ,
viewed_at TIMESTAMPTZ,
paid_at TIMESTAMPTZ,
paid_amount DECIMAL(12,2),
paid_currency TEXT,
-- PDF
pdf_url TEXT,
pdf_generated_at TIMESTAMPTZ,
-- Additional
notes TEXT, -- Internal notes
customer_notes TEXT, -- Visible on invoice
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, invoice_number),
-- Credit notes must reference an original invoice
CONSTRAINT credit_note_requires_original CHECK (
(invoice_type = 'credit_note' AND credited_invoice_id IS NOT NULL) OR
(invoice_type = 'invoice')
)
);
-- Indexes
CREATE INDEX idx_invoices_user_status ON invoices(user_id, status);
CREATE INDEX idx_invoices_due_date ON invoices(user_id, due_date);
CREATE INDEX idx_invoices_type ON invoices(user_id, invoice_type);
-- RLS
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own invoices"
ON invoices FOR ALL
USING (auth.uid() = user_id);
```
### invoice_items
```sql
CREATE TABLE invoice_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_id UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
description TEXT NOT NULL,
quantity DECIMAL(10,2) NOT NULL DEFAULT 1,
unit TEXT DEFAULT 'st', -- st, timmar, etc.
unit_price DECIMAL(12,2) NOT NULL,
-- Calculated
line_total DECIMAL(12,2) NOT NULL,
sort_order INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS
ALTER TABLE invoice_items ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own invoice items"
ON invoice_items FOR ALL
USING (
EXISTS (
SELECT 1 FROM invoices
WHERE invoices.id = invoice_items.invoice_id
AND invoices.user_id = auth.uid()
)
);
```
### salary_payments (Aktiebolag only)
Track owner salary payments for AB entities.
```sql
CREATE TABLE salary_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
-- Payment details
payment_date DATE NOT NULL,
pay_period_start DATE NOT NULL, -- e.g., 2024-01-01
pay_period_end DATE NOT NULL, -- e.g., 2024-01-31
-- Amounts
gross_salary DECIMAL(12,2) NOT NULL, -- Bruttolön
employer_contributions DECIMAL(12,2) NOT NULL, -- Arbetsgivaravgifter (31.42%)
withheld_tax DECIMAL(12,2) NOT NULL, -- Preliminärskatteavdrag
net_salary DECIMAL(12,2) NOT NULL, -- Nettolön (what's paid out)
-- Total cost to company
total_cost DECIMAL(12,2) NOT NULL, -- gross + employer_contributions
-- Reporting status
agi_reported BOOLEAN DEFAULT FALSE, -- AGI-deklaration submitted
agi_reported_at TIMESTAMPTZ,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS
ALTER TABLE salary_payments ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can CRUD own salary payments"
ON salary_payments FOR ALL
USING (auth.uid() = user_id);
-- Index for period queries
CREATE INDEX idx_salary_payments_period ON salary_payments(user_id, pay_period_start);
```
### Constants for AB calculations
```sql
-- Current rates (update annually)
-- Arbetsgivaravgifter: 31.42% (2024)
-- This should be configurable or fetched from a rates table
CREATE TABLE tax_rates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rate_type TEXT NOT NULL, -- 'arbetsgivaravgifter', 'bolagsskatt', etc.
rate DECIMAL(5,4) NOT NULL, -- 0.3142 for 31.42%
valid_from DATE NOT NULL,
valid_to DATE, -- NULL = currently active
UNIQUE(rate_type, valid_from)
);
-- Seed current rates
INSERT INTO tax_rates (rate_type, rate, valid_from) VALUES
('arbetsgivaravgifter', 0.3142, '2024-01-01'),
('bolagsskatt', 0.206, '2021-01-01'),
('egenavgifter', 0.2897, '2024-01-01');
```
### bas_accounts (Reference Table)
Available BAS accounts for transaction categorization.
```sql
CREATE TABLE bas_accounts (
account_number TEXT PRIMARY KEY, -- e.g., '5410'
name_sv TEXT NOT NULL, -- Swedish name
name_en TEXT, -- English name
account_class INTEGER NOT NULL, -- 1-8
account_group INTEGER NOT NULL, -- e.g., 54
-- Categorization
is_expense BOOLEAN DEFAULT FALSE,
is_income BOOLEAN DEFAULT FALSE,
is_asset BOOLEAN DEFAULT FALSE,
is_liability BOOLEAN DEFAULT FALSE,
-- Influencer-specific
common_for_influencers BOOLEAN DEFAULT FALSE,
risk_level TEXT, -- 'low', 'medium', 'high', 'very_high'
default_vat_rate DECIMAL(5,2),
-- Usage hints
description TEXT,
warning_text TEXT, -- Shown when selected
requires_documentation BOOLEAN DEFAULT FALSE,
active BOOLEAN DEFAULT TRUE
);
-- Seed common influencer accounts
INSERT INTO bas_accounts (account_number, name_sv, name_en, account_class, account_group, is_expense, common_for_influencers, risk_level, description) VALUES
('1210', 'Maskiner och inventarier', 'Equipment', 1, 12, FALSE, TRUE, 'low', 'Kameror, datorer över 29 400 kr'),
('2013', 'Eget uttag', 'Owner withdrawals', 2, 20, FALSE, TRUE, 'low', 'Privata utgifter betalda med företagskort'),
('3001', 'Försäljning 25%', 'Sales 25% VAT', 3, 30, FALSE, TRUE, 'low', 'Svenska samarbeten'),
('3044', 'Försäljning tjänst EU', 'EU service sales', 3, 30, FALSE, TRUE, 'low', 'AdSense, EU-affiliates. Kräver periodisk sammanställning'),
('3045', 'Försäljning tjänst export', 'Export service sales', 3, 30, FALSE, TRUE, 'low', 'Twitch, YouTube US. Momsfritt'),
('5410', 'Förbrukningsinventarier', 'Consumable equipment', 5, 54, TRUE, TRUE, 'low', 'Kameror, datorer under 29 400 kr'),
('5420', 'Programvaror', 'Software', 5, 54, TRUE, TRUE, 'low', 'Adobe CC, Epidemic Sound, SaaS'),
('5480', 'Arbetskläder', 'Work clothes', 5, 54, TRUE, TRUE, 'very_high', 'Endast skyddskläder/uniformer. EJ civila kläder!'),
('5800', 'Resekostnader', 'Travel expenses', 5, 58, TRUE, TRUE, 'medium', 'Tåg, flyg, hyrbil för tjänsteresor'),
('5820', 'Taxikostnader', 'Taxi', 5, 58, TRUE, TRUE, 'medium', 'Taxi/Uber för tjänsteresor'),
('5910', 'Annonsering', 'Advertising', 5, 59, TRUE, TRUE, 'low', 'Facebook Ads, Google Ads, TikTok'),
('6071', 'Representation, avdragsgill', 'Deductible representation', 6, 60, TRUE, TRUE, 'high', 'Max 60 kr/person. Kräver dokumentation'),
('6072', 'Representation, ej avdragsgill', 'Non-deductible representation', 6, 60, TRUE, TRUE, 'high', 'Måltider vid representation'),
('6230', 'Datakommunikation', 'Data/web hosting', 6, 62, TRUE, TRUE, 'low', 'Webbhotell, domäner, bredband'),
('6500', 'Övriga externa tjänster', 'Other services', 6, 65, TRUE, TRUE, 'low', 'Fotografer, klippare med F-skatt');
```
### mcc_mappings (Reference Table)
MCC code to BAS account mappings for auto-categorization.
```sql
CREATE TABLE mcc_mappings (
mcc_code TEXT PRIMARY KEY,
description TEXT NOT NULL,
risk_level TEXT NOT NULL, -- 'low', 'medium', 'high', 'very_high'
primary_account TEXT REFERENCES bas_accounts(account_number),
secondary_account TEXT REFERENCES bas_accounts(account_number),
requires_user_input BOOLEAN DEFAULT FALSE,
auto_reject_reason TEXT, -- If very_high risk, why?
categorization_logic TEXT,
active BOOLEAN DEFAULT TRUE
);
-- Seed MCC mappings (subset - see 08-BAS-ACCOUNTING-GUIDE.md for full list)
INSERT INTO mcc_mappings (mcc_code, description, risk_level, primary_account, secondary_account, requires_user_input, auto_reject_reason, categorization_logic) VALUES
('7311', 'Advertising Services', 'low', '5910', '5930', FALSE, NULL, 'Standard for Facebook/Google Ads. Triggers EU VAT check.'),
('5732', 'Electronics Stores', 'medium', '5410', '1210', FALSE, NULL, 'Amount determines routing. >29,400 kr → 1210.'),
('5946', 'Camera & Photo Supply', 'medium', '5410', '1210', FALSE, NULL, 'Same as electronics.'),
('5812', 'Eating Places/Restaurants', 'high', '6072', '2013', TRUE, NULL, 'Requires user input: Representation or private?'),
('5611', 'Mens Clothing', 'very_high', '2013', '5480', TRUE, 'Clothes usable privately are not deductible (RÅ81 1:29)', 'Default MUST be 2013.'),
('5621', 'Womens Ready-to-Wear', 'very_high', '2013', '5480', TRUE, 'Clothes usable privately are not deductible', 'Default MUST be 2013.'),
('5977', 'Cosmetic Stores', 'very_high', '2013', '5490', TRUE, 'Cosmetics are private expenses', 'Default private.'),
('4111', 'Commuter Transport', 'low', '5800', NULL, FALSE, NULL, 'Train/bus. 6% VAT in Sweden.'),
('4722', 'Travel Agencies', 'medium', '5800', '2013', TRUE, NULL, 'Is the trip business-related?'),
('7372', 'Computer Programming', 'low', '6230', '5420', FALSE, NULL, 'Web hosting, SaaS.'),
('5921', 'Package Stores (Systembolaget)', 'very_high', '2013', NULL, FALSE, 'Alcohol is not deductible', 'Always 2013.'),
('7941', 'Sports Clubs/Gyms', 'very_high', '2013', NULL, FALSE, 'Gym is private expense even for fitness influencers', 'Always 2013.');
```
## Functions
### Calculate invoice totals
```sql
CREATE OR REPLACE FUNCTION calculate_invoice_totals()
RETURNS TRIGGER AS $$
BEGIN
UPDATE invoices
SET
subtotal = (
SELECT COALESCE(SUM(line_total), 0)
FROM invoice_items
WHERE invoice_id = NEW.invoice_id
),
vat_amount = (
SELECT COALESCE(SUM(line_total), 0) * (vat_rate / 100)
FROM invoice_items
WHERE invoice_id = NEW.invoice_id
),
total = (
SELECT COALESCE(SUM(line_total), 0) * (1 + vat_rate / 100)
FROM invoice_items
WHERE invoice_id = NEW.invoice_id
),
updated_at = NOW()
WHERE id = NEW.invoice_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER trigger_calculate_invoice_totals
AFTER INSERT OR UPDATE OR DELETE ON invoice_items
FOR EACH ROW
EXECUTE FUNCTION calculate_invoice_totals();
```
### Auto-create profile on signup
```sql
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO profiles (id, email, full_name)
VALUES (
NEW.id,
NEW.email,
NEW.raw_user_meta_data->>'full_name'
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION handle_new_user();
```
## Views
### Dashboard summary
```sql
CREATE VIEW dashboard_summary AS
SELECT
p.id AS user_id,
-- Revenue YTD
COALESCE(SUM(
CASE WHEN t.amount > 0 AND t.category = 'business_income'
AND t.booking_date >= DATE_TRUNC('year', CURRENT_DATE)
THEN t.amount ELSE 0 END
), 0) AS revenue_ytd,
-- Expenses YTD
COALESCE(SUM(
CASE WHEN t.amount < 0 AND t.category = 'business_expense'
AND t.booking_date >= DATE_TRUNC('year', CURRENT_DATE)
THEN ABS(t.amount) ELSE 0 END
), 0) AS expenses_ytd,
-- Uncategorized count
COUNT(CASE WHEN t.category = 'uncategorized' THEN 1 END) AS uncategorized_count,
-- Unpaid invoices
(SELECT COUNT(*) FROM invoices i
WHERE i.user_id = p.id
AND i.status IN ('sent', 'overdue')) AS unpaid_invoice_count,
(SELECT COALESCE(SUM(total), 0) FROM invoices i
WHERE i.user_id = p.id
AND i.status IN ('sent', 'overdue')) AS unpaid_invoice_total
FROM profiles p
LEFT JOIN transactions t ON t.user_id = p.id
GROUP BY p.id;
```
## Migration File
Save as `supabase/migrations/001_initial_schema.sql`:
```sql
-- Copy all CREATE TABLE, CREATE TYPE, CREATE FUNCTION, CREATE VIEW statements above
-- Run with: supabase db push
```
+672
View File
@@ -0,0 +1,672 @@
# API Specification
## Authentication
All API routes require authentication via Supabase session cookie.
```typescript
// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
},
}
)
}
// Usage in route
export async function GET() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
// ... route logic
}
```
---
## Endpoints
### Company Settings
#### GET /api/settings
Returns current user's company settings.
**Response 200:**
```json
{
"id": "uuid",
"company_name": "Anna Andersson Content AB",
"org_number": "123456-7890",
"vat_number": "SE1234567890",
"vat_registered": true,
"address_line1": "Storgatan 1",
"city": "Stockholm",
"postal_code": "11122",
"payment_terms_days": 30,
"next_invoice_number": 15
}
```
#### PUT /api/settings
Updates company settings.
**Request:**
```json
{
"company_name": "Anna Andersson Content AB",
"municipal_tax_rate": 31.5,
"payment_terms_days": 14
}
```
---
### Transactions
#### GET /api/transactions
List transactions with optional filters.
**Query params:**
- `category`: filter by category
- `from`: start date (YYYY-MM-DD)
- `to`: end date
- `uncategorized`: boolean, only uncategorized
- `limit`: default 50
- `offset`: pagination
**Response 200:**
```json
{
"data": [
{
"id": "uuid",
"booking_date": "2024-01-15",
"amount": -1500.00,
"currency": "SEK",
"description": "ADOBE SYSTEMS",
"counterparty_name": "Adobe",
"category": "uncategorized",
"expense_type": null
}
],
"total": 142,
"uncategorized_count": 23
}
```
#### PATCH /api/transactions/:id
Categorize a transaction.
**Request:**
```json
{
"category": "business_expense",
"expense_type": "software",
"business_percentage": 100,
"notes": "Creative Cloud subscription"
}
```
#### POST /api/transactions/bulk-categorize
Categorize multiple transactions at once.
**Request:**
```json
{
"transaction_ids": ["uuid1", "uuid2"],
"category": "private"
}
```
---
### Banking
#### POST /api/banking/connect
Initiates PSD2 bank connection flow.
**Request:**
```json
{
"provider": "tink"
}
```
**Response 200:**
```json
{
"redirect_url": "https://link.tink.com/..."
}
```
#### GET /api/banking/callback
OAuth callback from banking provider. Handles token exchange.
**Query params:** Provider-specific (code, state, etc.)
**Redirects to:** `/transactions?connected=true`
#### POST /api/banking/sync
Manually trigger transaction sync. Also called by cron.
**Response 200:**
```json
{
"synced": 12,
"new_transactions": 3
}
```
---
### Customers
#### GET /api/customers
List all customers.
**Response 200:**
```json
{
"data": [
{
"id": "uuid",
"name": "Influencer Agency AB",
"customer_type": "swedish_business",
"org_number": "556677-8899",
"country": "SE",
"invoice_count": 5
}
]
}
```
#### POST /api/customers
Create new customer.
**Request:**
```json
{
"name": "Google Ireland Ltd",
"customer_type": "eu_business",
"vat_number": "IE6388047V",
"address_line1": "Gordon House",
"city": "Dublin",
"country": "IE",
"email": "payments@google.com"
}
```
**Response 201:** Created customer object
#### GET /api/customers/:id
Get single customer with invoice history.
#### PUT /api/customers/:id
Update customer.
#### DELETE /api/customers/:id
Soft delete (only if no invoices linked).
---
### VAT Validation
#### POST /api/vat/validate
Validate EU VAT number via VIES (VAT Information Exchange System).
**Critical for Reverse Charge:** Swedish law requires validated VAT number before applying 0% rate on EU B2B sales. Without validation, you must charge 25% Swedish VAT.
**Request:**
```json
{
"vat_number": "IE6388047V"
}
```
**Response 200:**
```json
{
"valid": true,
"country_code": "IE",
"vat_number": "6388047V",
"name": "GOOGLE IRELAND LIMITED",
"address": "GORDON HOUSE, BARROW STREET, DUBLIN 4",
"validated_at": "2024-01-15T10:30:00Z"
}
```
**Response 200 (Invalid):**
```json
{
"valid": false,
"country_code": "IE",
"vat_number": "INVALID123",
"error": "VAT number not found in VIES database"
}
```
**Implementation Notes:**
- Use EU VIES SOAP service or REST wrapper
- Cache validation for 24 hours (VAT numbers rarely change)
- Store `validated_at` timestamp on customer record
- Re-validate periodically for long-term customers
---
### Foreign Purchase Handling (Fiktiv Moms)
#### POST /api/transactions/:id/foreign-purchase
Mark a transaction as foreign service purchase requiring fiktiv moms.
When influencers buy services from abroad (Adobe CC, Facebook Ads, AWS), they must self-report VAT.
**Request:**
```json
{
"supplier_country": "US",
"service_type": "digital_service",
"vat_rate": 25
}
```
**Response 200:**
```json
{
"transaction_id": "uuid",
"fiktiv_moms_amount": 375.00,
"bookings": [
{ "account": "2645", "debit": 375.00, "description": "Beräknad ingående moms utländskt förvärv" },
{ "account": "2614", "credit": 375.00, "description": "Utgående moms utländskt förvärv" }
],
"moms_ruta_21": 1500.00,
"moms_ruta_48": 375.00
}
```
**Momsdeklaration Impact:**
- Ruta 21: Purchase amount (before VAT)
- Ruta 48: Self-reported output VAT
- Net effect is zero if input VAT is deductible, but both must be declared
```
---
### Invoices
#### GET /api/invoices
List invoices with filters.
**Query params:**
- `status`: draft, sent, paid, overdue
- `customer_id`: filter by customer
- `from`, `to`: date range
**Response 200:**
```json
{
"data": [
{
"id": "uuid",
"invoice_number": "INV-2024-015",
"customer": {
"id": "uuid",
"name": "Agency AB"
},
"invoice_date": "2024-01-15",
"due_date": "2024-02-14",
"total": 25000.00,
"currency": "SEK",
"status": "sent"
}
]
}
```
#### POST /api/invoices
Create new invoice.
**Request:**
```json
{
"customer_id": "uuid",
"invoice_date": "2024-01-15",
"due_date": "2024-02-14",
"reference": "PO-12345",
"items": [
{
"description": "Instagram kampanj November",
"quantity": 1,
"unit": "st",
"unit_price": 20000.00
}
],
"customer_notes": "Tack för samarbetet!"
}
```
The API automatically determines VAT treatment based on customer type:
```typescript
// lib/invoice/vat-rules.ts
interface VatDecision {
rate: number
reverseCharge: boolean
invoiceText: string | null
momsRuta: number // Which ruta in momsdeklaration
}
function determineVatTreatment(customer: Customer, settings: CompanySettings): VatDecision {
// User not VAT registered - no VAT on any invoice
if (!settings.vat_registered) {
return {
rate: 0,
reverseCharge: false,
invoiceText: 'Säljaren är inte momsregistrerad',
momsRuta: 0 // Not applicable
}
}
switch (customer.customer_type) {
case 'swedish_business':
case 'individual':
return {
rate: 25,
reverseCharge: false,
invoiceText: null,
momsRuta: 5 // Ruta 05: Momspliktig försäljning
}
case 'eu_business':
if (customer.vat_number_validated) {
return {
rate: 0,
reverseCharge: true,
invoiceText: 'Omvänd skattskyldighet / Reverse charge - Article 196 Council Directive 2006/112/EC',
momsRuta: 39 // Ruta 39: Tjänsteförsäljning EU
}
}
// EU business without valid VAT = charge Swedish VAT
return {
rate: 25,
reverseCharge: false,
invoiceText: null,
momsRuta: 5
}
case 'non_eu_business':
return {
rate: 0,
reverseCharge: false,
invoiceText: 'Export av tjänst - moms utgår ej',
momsRuta: 40 // Ruta 40: Export
}
}
}
```
**Momsdeklaration Ruta Reference:**
| Ruta | Description | When Used |
|------|-------------|-----------|
| 05 | Momspliktig försäljning | Swedish domestic sales |
| 39 | Tjänsteförsäljning EU | EU B2B with reverse charge |
| 40 | Export | Non-EU sales |
| 21 | Inköp av tjänster från EU | Foreign service purchases (input) |
| 48 | Utgående moms på inköp | Fiktiv moms on foreign purchases |
**EU Sales Reporting:**
Transactions with `momsRuta: 39` must be aggregated into Periodisk Sammanställning (quarterly report to Skatteverket), separate from regular momsdeklaration.
**Response 201:** Created invoice with calculated totals
#### GET /api/invoices/:id
Get full invoice details including items.
#### PUT /api/invoices/:id
Update draft invoice. Cannot modify sent/paid invoices.
#### POST /api/invoices/:id/send
Mark invoice as sent and optionally email to customer.
**Request:**
```json
{
"send_email": true,
"email_to": "invoice@client.com",
"email_message": "Hej! Här kommer fakturan för vårt samarbete."
}
```
**Email Implementation:** Uses Supabase built-in email via Edge Functions.
**Response 200:**
```json
{
"id": "uuid",
"status": "sent",
"sent_at": "2024-01-15T10:30:00Z",
"email_sent": true
}
```
**Note:** Invoice PDF attached to email. Payment method shown: bank transfer only (Bankgiro/IBAN). Future: third-party payment links (Stripe, Klarna).
#### POST /api/invoices/:id/mark-paid
Mark invoice as paid.
**Request:**
```json
{
"paid_at": "2024-02-10",
"paid_amount": 25000.00,
"linked_transaction_id": "uuid"
}
```
#### GET /api/invoices/:id/pdf
Generate and return invoice PDF.
**Query params:**
- `regenerate`: force regenerate cached PDF
**Response:** PDF file or redirect to signed storage URL
---
### Salary Payments (Aktiebolag only)
#### GET /api/salary
List salary payments for current fiscal year.
**Response 200:**
```json
{
"data": [
{
"id": "uuid",
"payment_date": "2024-01-25",
"pay_period_start": "2024-01-01",
"pay_period_end": "2024-01-31",
"gross_salary": 50000.00,
"employer_contributions": 15710.00,
"withheld_tax": 15000.00,
"net_salary": 35000.00,
"total_cost": 65710.00,
"agi_reported": false
}
],
"ytd_totals": {
"gross_salary": 50000.00,
"employer_contributions": 15710.00,
"withheld_tax": 15000.00,
"total_cost": 65710.00
}
}
```
#### POST /api/salary
Create salary payment record.
**Request:**
```json
{
"pay_period_start": "2024-01-01",
"pay_period_end": "2024-01-31",
"gross_salary": 50000.00,
"tax_table": 33
}
```
**Automatic calculations:**
- `employer_contributions`: gross × 31.42%
- `withheld_tax`: from Swedish tax tables based on gross + table number
- `net_salary`: gross - withheld_tax
- `total_cost`: gross + employer_contributions
**Response 201:** Created salary payment with all calculated fields.
#### POST /api/salary/:id/mark-agi-reported
Mark salary payment as reported in AGI-deklaration.
**Response 200:**
```json
{
"id": "uuid",
"agi_reported": true,
"agi_reported_at": "2024-02-12T14:00:00Z"
}
```
---
### Dashboard
#### GET /api/dashboard/summary
Get aggregated financial overview.
**Response 200:**
```json
{
"period": {
"year": 2024,
"month": 1
},
"revenue": {
"ytd": 450000.00,
"this_month": 75000.00
},
"expenses": {
"ytd": 85000.00,
"this_month": 12000.00
},
"net_income": {
"ytd": 365000.00
},
"tax_estimate": {
"egenavgifter": 105745.00,
"income_tax": 116800.00,
"vat_payable": 91250.00,
"total_locked": 313795.00
},
"disponibelt": 136205.00,
"vat_threshold": {
"limit": 80000,
"current": 450000.00,
"registered": true
},
"pending": {
"uncategorized_transactions": 23,
"unpaid_invoices": 2,
"unpaid_amount": 45000.00
}
}
```
#### GET /api/dashboard/chart
Monthly revenue/expense data for charts.
**Query params:**
- `months`: number of months (default 12)
**Response 200:**
```json
{
"data": [
{
"month": "2024-01",
"revenue": 75000,
"expenses": 12000,
"net": 63000
}
]
}
```
---
## Error Responses
All endpoints return errors in consistent format:
```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid VAT number format",
"details": {
"field": "vat_number",
"value": "invalid"
}
}
}
```
**Common status codes:**
- 400: Validation error
- 401: Not authenticated
- 403: Forbidden (RLS violation)
- 404: Resource not found
- 409: Conflict (duplicate invoice number)
- 500: Internal error
+728
View File
@@ -0,0 +1,728 @@
# UI/UX Specification
## Design Principles
1. **Calm over busy**: Financial apps induce anxiety. Use whitespace, muted colors, clear hierarchy.
2. **One action per screen**: Each view has one primary action.
3. **Instant feedback**: Every action shows immediate result.
4. **Mobile-first**: Influencers manage finances on phone.
Color System
CSS
:root {
/* Primary - Deep Forest & Sage (Trustworthy but aesthetic) */
--primary-50: #f4fbf7;
--primary-500: #4d8b73;
--primary-600: #3a6b58;
--primary-700: #2c4f42;
/* Success - Muted Mint (Profit/Growth) */
--success-500: #10b981;
/* Warning - Burnt Amber (Pending/Alerts) */
--warning-500: #d97706;
/* Danger - Soft Brick (Expenses/Loss) */
--danger-500: #e11d48;
/* Neutral - "Stone" (Warmer than standard gray, feels like paper) */
--gray-50: #fafaf9; /* Background base */
--gray-100: #f5f5f4; /* Card background */
--gray-200: #e7e5e4; /* Borders */
--gray-500: #78716c; /* Secondary text */
--gray-900: #1c1917; /* Primary text (Soft Black) */
}
Typography
Headings: Plus Jakarta Sans or Outfit (600/700 weight) — More editorial feel
Body: Inter (400/500 weight) — Clean readability
Numbers/Money: JetBrains Mono or Inter (Tabular figures enabled)
## Page Layouts
### Onboarding Wizard (/onboarding)
Full-screen wizard. User cannot access app until complete. Progress saved between sessions.
```
┌─────────────────────────────────────────────────────────────┐
│ │
│ Logo Steg 1 av 6 │
│ │
│ ━━━━━━━━━━○○○○○○ │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ VÄLKOMMEN! LÅT OSS KOMMA IGÅNG │
│ │
│ Vilken bolagsform har du? │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ │ │
│ │ ○ Enskild firma │ │
│ │ Enskild näringsidkare │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ │ │
│ │ ○ Aktiebolag (AB) │ │
│ │ Eget bolag med org.nummer │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
│ │
│ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Fortsätt → │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Wizard Steps:**
1. **Entity Type** - Enskild firma or Aktiebolag
2. **Company Details** - Name, org.nr (required for AB), address
3. **Tax Registration** - F-skatt status, VAT registered?, momsperiod
4. **Preliminary Tax** - Monthly F-skatt amount (for comparison warnings)
5. **Bank Details** - Account info for invoice payments (IBAN/BIC or Swedish format)
6. **Connect Bank** - Enable Banking integration (at least one account required)
```
STEP 2: FÖRETAGSUPPGIFTER
┌─────────────────────────────────────────────────────────────┐
│ │
│ Företagsnamn * │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Anna Andersson Content │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Organisationsnummer * (för AB) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 559123-4567 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Adress * │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Storgatan 1 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Postnummer * Ort * │
│ ┌──────────────┐ ┌────────────────────────────┐ │
│ │ 111 22 │ │ Stockholm │ │
│ └──────────────┘ └────────────────────────────┘ │
│ │
│ │
│ ┌──────────────┐ ┌─────────────────────────────────┐ │
│ │ ← Tillbaka │ │ Fortsätt → │ │
│ └──────────────┘ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
```
STEP 3: SKATTEREGISTRERING
┌─────────────────────────────────────────────────────────────┐
│ │
│ ☑ Jag har F-skattsedel │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ Räkenskapsår * │
│ ○ Kalenderår (jan-dec) │
│ ○ Brutet räkenskapsår │
│ │
│ [Visas om brutet:] │
│ Räkenskapsåret börjar * │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Juli (1 jul - 30 jun) ▼ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ Är du momsregistrerad? * │
│ │
│ ○ Ja, jag är momsregistrerad │
│ ○ Nej, min omsättning är under 80 000 kr/år │
│ │
│ [Visas om momsregistrerad:] │
│ │
│ Momsnummer │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SE559123456701 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Momsperiod * │
│ ○ Kvartalsvis (vanligast) │
│ ○ Månadsvis │
│ │
└─────────────────────────────────────────────────────────────┘
```
```
STEP 4: PRELIMINÄRSKATT
┌─────────────────────────────────────────────────────────────┐
│ │
│ Hur mycket betalar du i F-skatt per månad? │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 15 000 kr │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⓘ Vi använder detta för att varna dig om din beräknade │
│ skatt skiljer sig mycket från det du betalar in. │
│ │
│ Hittar du det på: │
│ • Ditt senaste F-skattbeslut från Skatteverket │
│ • Skattekontot på skatteverket.se │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Jag vet inte just nu - hoppa över │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
```
STEP 6: KOPPLA BANK
┌─────────────────────────────────────────────────────────────┐
│ │
│ Koppla ditt företagskonto │
│ │
│ Vi hämtar dina transaktioner automatiskt så du slipper │
│ mata in dem manuellt. │
│ │
│ 🔒 Säkert via BankID. Vi kan aldrig flytta dina pengar. │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 🏦 Koppla bankkonto │ │
│ └─────────────────────────────────────┘ │
│ │
│ Populära banker: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Nordea │ │ SEB │ │Swedbank │ │ Handels │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ⓘ Du kan koppla fler konton senare i inställningar. │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Validation Rules:**
- All fields marked * are required
- Org.nr validated against format (NNNNNN-NNNN)
- At least one bank account must be connected
- Cannot proceed without completing current step
---
### Dashboard (/)
```
┌─────────────────────────────────────────────────────────────┐
│ Logo [Settings] [Avatar]│
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ DISPONIBELT ATT SPENDERA │ │
│ │ │ │
│ │ 136 205 kr │ │
│ │ ─────────────────── │ │
│ │ │ │
│ │ Total: 450 000 kr │ │
│ │ ┌────────────────────────────┬──────────────┐ │ │
│ │ │██████████████████████████ │░░░░░░░░░░░░░░│ │ │
│ │ │ Disponibelt 30% │ Låst 70% │ │ │
│ │ └────────────────────────────┴──────────────┘ │ │
│ │ │ │
│ │ [Visa skatteberäkning ↓] │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ ⚠️ 23 transaktioner │ │ 📄 2 obetalda │ │
│ │ att sortera │ │ fakturor │ │
│ │ │ │ 45 000 kr │ │
│ │ [Sortera nu →] │ │ [Visa →] │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ INTÄKTER 2024 ┌────────────────────────┐ │
│ ────────────── │ ____│ │
│ 450 000 kr │ ____/ │ │
│ │ ____/ │ │
│ │ ____/ │ │
│ │____/ │ │
│ └────────────────────────┘ │
│ Jan Feb Mar Apr May Jun │
│ │
└─────────────────────────────────────────────────────────────┘
│ [Dashboard] [Transaktioner] [Fakturor] [+] │
└─────────────────────────────────────────────────────────────┘
```
**Components:**
1. **BalanceCard**: Hero component showing available balance
- Large number, animated on load
- Expandable tax breakdown panel
- Color-coded bar chart (green=available, gray=locked)
2. **AlertCard**: Action items requiring attention
- Badge with count
- Clear CTA button
- Subtle warning color for urgency
3. **RevenueChart**: Simple line/area chart
- Recharts or Chart.js
- Hover shows month details
- No unnecessary decoration
---
### Tax Breakdown (Expanded)
```
┌─────────────────────────────────────────────────────────────┐
│ │
│ SKATTEBERÄKNING 2024 [Så räknar vi] │
│ ───────────────────────────────────────────────────────── │
│ │
│ Intäkter 450 000 kr │
│ - Avdragsgilla kostnader -85 000 kr │
│ ───────────────────────────────────────────────────────── │
│ = Resultat 365 000 kr │
│ │
│ Egenavgifter (28,97%) -105 745 kr ░░░░ │
│ Inkomstskatt (~32%) -116 800 kr ░░░░░ │
│ Moms att betala -91 250 kr ░░░░ │
│ ───────────────────────────────────────────────────────── │
│ = Låst för skatt 313 795 kr │
│ │
│ ⓘ Detta är en uppskattning. Faktisk skatt kan variera. │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
### Transactions - Swipe View (/transactions)
```
┌─────────────────────────────────────────────────────────────┐
│ ← Tillbaka Sortera transaktioner │
├─────────────────────────────────────────────────────────────┤
│ │
│ 23 kvar att sortera │
│ │
│ ← PRIVAT FÖRETAG → │
│ │
│ ┌─────────────────────────┐ │
│ │ │ │
│ │ ADOBE SYSTEMS │ │
│ │ -1 500,00 kr │ │
│ │ │ │
│ │ 15 jan 2024 │ │
│ │ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ Företag │ │ │
│ │ │ [▼ Välj typ] │ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────────┘ │
│ │
│ [Osäker?] │
│ │
│ ──────────────────────────────────────────────────────────│
│ │
│ Redan sorterade [Visa alla] │
│ │
│ ● Adobe Systems -1 500 kr Programvara ✓ │
│ ● Spotify AB -149 kr Privat ✓ │
│ ● Influencer AB +25 000 kr Inkomst ✓ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Interaction:**
- Swipe right = Business (show expense type selector)
- Swipe left = Private
- Tap "Osäker?" = Mark for later / add note
- Cards animate smoothly with spring physics
**Components:**
1. **SwipeCard**: Draggable card with gesture handling
- Use `@use-gesture/react` + `framer-motion`
- Threshold: 100px triggers categorization
- Background color hints direction
2. **ExpenseTypeSelector**: Dropdown/bottom sheet
- Only shown for business expenses
- Quick-select common types
---
### Transactions - List View (/transactions?view=list)
```
┌─────────────────────────────────────────────────────────────┐
│ Transaktioner [Filter] [+ Manual] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🔍 Sök transaktioner... │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [Alla] [Osorterade (23)] [Företag] [Privat] │
│ │
│ JANUARI 2024 │
│ ───────────────────────────────────────────────────────── │
│ │
│ 15 jan ADOBE SYSTEMS -1 500,00 kr │
│ Software subscription [Programvara] → │
│ │
│ 14 jan Influencer Agency AB +25 000,00 kr │
│ Faktura #INV-2024-012 [Inkomst] → │
│ │
│ 12 jan ICA MAXI -847,00 kr │
│ Osorterad [Sortera] → │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
### Create Invoice (/invoices/new)
```
┌─────────────────────────────────────────────────────────────┐
│ ← Avbryt Ny faktura [Förhandsv]│
├─────────────────────────────────────────────────────────────┤
│ │
│ KUND │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🔍 Sök eller skapa ny kund... │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Google Ireland │ │ Influencer AB │ │
│ │ EU-företag │ │ Svenskt företag │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ FAKTURADETALJER │
│ │
│ Fakturadatum Förfallodatum │
│ ┌────────────┐ ┌────────────┐ │
│ │ 2024-01-15 │ │ 2024-02-14 │ (30 dagar) │
│ └────────────┘ └────────────┘ │
│ │
│ Er referens │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PO-12345 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ RADER │
│ │
│ Beskrivning Antal À-pris │
│ ┌────────────────────────────────┐ ┌────┐ ┌─────────┐ │
│ │ Instagram kampanj November │ │ 1 │ │ 20 000 │ │
│ └────────────────────────────────┘ └────┘ └─────────┘ │
│ = 20 000 kr │
│ │
│ [+ Lägg till rad] │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ Summa exkl. moms 20 000,00 kr │
│ Moms 25% 5 000,00 kr │
│ ───────────────────────────────────────────────────────── │
│ ATT BETALA 25 000,00 kr │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [Spara utkast] [Skapa & skicka] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**VAT Logic Display:**
For EU customer with valid VAT:
```
│ Summa 20 000,00 kr │
│ Moms 0,00 kr │
│ ───────────────────────────────────────────────────────── │
│ ATT BETALA 20 000,00 kr │
│ │
│ ⓘ Omvänd skattskyldighet tillämpas (EU B2B) │
```
**Components:**
1. **CustomerSelect**: Searchable dropdown with recent customers
- Shows customer type badge
- "Create new" option at bottom
2. **InvoiceLineItem**: Repeatable row
- Auto-calculate line total
- Drag to reorder
3. **VatSummary**: Auto-updating totals
- Shows applicable VAT rule
- Explanation tooltip
---
### Invoice Preview (Modal/Slide-over)
```
┌─────────────────────────────────────────────────────────────┐
│ Förhandsgranskning [✕ Stäng] │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ANNA ANDERSSON CONTENT │ │
│ │ Storgatan 1, 111 22 Stockholm │ │
│ │ F-skatt: Ja | Moms: SE123456789001 │ │
│ │ │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ │ │
│ │ FAKTURA │ │
│ │ Fakturanummer: INV-2024-015 │ │
│ │ Fakturadatum: 2024-01-15 │ │
│ │ Förfallodatum: 2024-02-14 │ │
│ │ │ │
│ │ Till: │ │
│ │ Google Ireland Limited │ │
│ │ Gordon House, Barrow Street │ │
│ │ Dublin 4, Ireland │ │
│ │ VAT: IE6388047V │ │
│ │ │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ │ │
│ │ Beskrivning Antal Pris Summa │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ YouTube AdSense Jan 2024 1 st €1,850 €1,850 │ │
│ │ │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ Summa exkl. moms €1,850 │ │
│ │ Moms (0%) €0 │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ ATT BETALA €1,850 │ │
│ │ │ │
│ │ Reverse charge - Article 196 Council Directive │ │
│ │ 2006/112/EC │ │
│ │ │ │
│ │ ───────────────────────────────────────────────── │ │
│ │ Betalning till: IBAN SE12 3456 7890 1234 5678 90 │ │
│ │ BIC: SWEDSESS │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ [Ladda ner PDF] [Skicka via email] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
### Salary Management (/salary) - Aktiebolag only
Only visible for users with entity_type = 'aktiebolag'.
```
┌─────────────────────────────────────────────────────────────┐
│ ← Tillbaka Lön & Arbetsgivaravgifter│
├─────────────────────────────────────────────────────────────┤
│ │
│ 2024 [+ Registrera lön]│
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HITTILLS I ÅR │ │
│ │ │ │
│ │ Bruttolön 150 000 kr │ │
│ │ Arbetsgivaravgifter 47 130 kr │ │
│ │ ───────────────────────────────── │ │
│ │ Total lönekostnad 197 130 kr │ │
│ │ │ │
│ │ Innehållen skatt 45 000 kr ⚠️ Att betala in │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ LÖNEUTBETALNINGAR │
│ ───────────────────────────────────────────────────────── │
│ │
│ Mars 2024 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Brutto 50 000 kr Arb.avg 15 710 kr │ │
│ │ Skatt -15 000 kr Totalt 65 710 kr │ │
│ │ ───────────────── │ │
│ │ Netto 35 000 kr AGI: ✓ Rapporterad │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Februari 2024 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Brutto 50 000 kr Arb.avg 15 710 kr │ │
│ │ Skatt -15 000 kr Totalt 65 710 kr │ │
│ │ ───────────────── │ │
│ │ Netto 35 000 kr AGI: ⚠️ Ej rapporterad │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Register Salary Modal:**
```
┌─────────────────────────────────────────────────────────────┐
│ Registrera löneutbetalning [✕] │
├─────────────────────────────────────────────────────────────┤
│ │
│ Löneperiod * │
│ ┌──────────────┐ till ┌──────────────┐ │
│ │ 2024-03-01 │ │ 2024-03-31 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Bruttolön * │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 50 000 kr │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Skattetabell * │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tabell 33 - Stockholm ▼ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ⓘ Hittas på ditt skattsedelsbesked │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ BERÄKNAT │
│ Arbetsgivaravgifter (31,42%) 15 710 kr │
│ Preliminärskatt (enligt tabell) 15 000 kr │
│ ───────────────────────────────────────────────────────── │
│ Nettolön till dig 35 000 kr │
│ Total kostnad för bolaget 65 710 kr │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Registrera löneutbetalning │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
### Settings (/settings)
```
┌─────────────────────────────────────────────────────────────┐
│ ← Tillbaka Inställningar │
├─────────────────────────────────────────────────────────────┤
│ │
│ FÖRETAGSINFORMATION │
│ │
│ Företagsnamn │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Anna Andersson Content │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ☑ Jag har F-skattsedel │
│ ☑ Jag är momsregistrerad │
│ │
│ Momsnummer │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ SE199001011234 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ SKATTEBERÄKNING │
│ │
│ Kommunalskattesats │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 31.5 % [Hitta min →] │ │
│ └─────────────────────────────────────────────────────┘ │
│ ⓘ Används för att beräkna ungefärlig inkomstskatt │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ FAKTURAINSTÄLLNINGAR │
│ │
│ Betalningsvillkor (dagar) │
│ ┌────────┐ │
│ │ 30 │ │
│ └────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ BANKUPPGIFTER (visas på fakturor) │
│ │
│ Bank Clearingnummer Kontonummer │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Nordea │ │ 3300 │ │ 123 456 789 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ KOPPLAD BANK │
│ │
│ ✓ Nordea ****4567 [Koppla bort] │
│ Senast synkad: idag 08:15 │
│ │
│ [Spara ändringar] │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Component Library
Use shadcn/ui as base. Key components needed:
### Core
- Button (primary, secondary, ghost, destructive)
- Input (text, number, date)
- Select (with search)
- Checkbox
- Card
- Badge
- Dialog / Sheet (slide-over)
- Toast (notifications)
### Custom
- SwipeCard (gesture-based)
- MoneyDisplay (formatted SEK/EUR with color)
- StatusBadge (invoice/transaction status)
- PercentageBar (disponibelt visualization)
- DatePicker (Swedish locale)
---
## Responsive Behavior
| Breakpoint | Layout |
|------------|--------|
| < 640px (mobile) | Single column, bottom nav, swipe UI default |
| 640-1024px (tablet) | Two columns where appropriate |
| > 1024px (desktop) | Sidebar nav, multi-column dashboard |
Mobile is primary. All features must work on 375px width.
---
## Accessibility
- All interactive elements keyboard accessible
- ARIA labels for icons
- Color contrast AA minimum
- Focus indicators visible
- Screen reader tested
File diff suppressed because it is too large Load Diff
+519
View File
@@ -0,0 +1,519 @@
# Future Features & Regulatory Integrations
## Phase 2: SIE File Import (Migration)
### Overview
SIE (Standard Import Export) is the Swedish standard for accounting data exchange. Users migrating from Fortnox, Visma, or other systems can export their data as SIE files.
### SIE File Types
| Type | Content | Use Case |
|------|---------|----------|
| SIE1 | Årssaldon | Year-end balances only |
| SIE2 | Periodsaldon | Monthly/periodic balances |
| SIE3 | Objektsaldon | Balances with cost centers |
| SIE4 | Transaktioner | Full transaction history |
**Recommended:** Support SIE4 for complete migration, SIE1/2 for simpler imports.
### Implementation Notes
```typescript
// lib/import/sie-parser.ts
interface SIETransaction {
verifikationsnummer: string
datum: Date
kontonummer: string
belopp: number
text: string
}
interface SIEImportResult {
company: {
name: string
orgNumber: string
fiscalYearStart: Date
}
accounts: Array<{ number: string; name: string }>
transactions: SIETransaction[]
balances: Array<{ account: string; balance: number }>
}
// SIE files use CP437 encoding and specific format
// Example line: #VER A 1 20240115 "Faktura 1001"
```
### Mapping Decisions Needed
Before implementing, determine mapping for:
- BAS account numbers → system categories
- Opening balances → how to represent
- Customer/supplier data (if present in SIE)
- Historical invoices → create as "imported" status?
---
## Phase 2: Benefits & Gifts Module (Förmånshantering)
### Problem
Influencers receive products ("PR-utskick", "gifted collabs") that may be taxable income. Skatteverket actively audits this. Getting it wrong = skattetillägg.
### Decision Tree Logic
```typescript
// lib/benefits/gift-classifier.ts
interface GiftClassification {
taxable: boolean
marketValue: number
deductibleAsExpense: boolean
bookingType: 'income' | 'income_and_expense' | 'tax_free'
reasoning: string
}
interface GiftInput {
estimatedValue: number
hasMotprestation: boolean // Required post/video/mention?
usedInBusiness: boolean // Used as props/equipment?
usedPrivately: boolean // Personal use?
isSimplePromoItem: boolean // Pen, mug, basic merch?
}
const TAX_FREE_PROMO_THRESHOLD = 450 // SEK, Skatteverket guideline
export function classifyGift(input: GiftInput): GiftClassification {
// Rule 1: Simple promotional items under threshold = tax free
if (input.isSimplePromoItem && input.estimatedValue <= TAX_FREE_PROMO_THRESHOLD && !input.hasMotprestation) {
return {
taxable: false,
marketValue: input.estimatedValue,
deductibleAsExpense: false,
bookingType: 'tax_free',
reasoning: 'Enklare reklamgåva under 450 kr utan krav på motprestation'
}
}
// Rule 2: Motprestation exists = always taxable
if (input.hasMotprestation) {
if (input.usedInBusiness && !input.usedPrivately) {
return {
taxable: true,
marketValue: input.estimatedValue,
deductibleAsExpense: true,
bookingType: 'income_and_expense',
reasoning: 'Skattepliktig förmån (motprestation krävdes). Avdragsgill som rekvisita då den endast används i verksamheten.'
}
}
return {
taxable: true,
marketValue: input.estimatedValue,
deductibleAsExpense: false,
bookingType: 'income',
reasoning: 'Skattepliktig förmån (motprestation krävdes). Ej avdragsgill då produkten används privat.'
}
}
// Rule 3: High value without motprestation but used privately
if (input.estimatedValue > TAX_FREE_PROMO_THRESHOLD && input.usedPrivately) {
return {
taxable: true,
marketValue: input.estimatedValue,
deductibleAsExpense: false,
bookingType: 'income',
reasoning: 'Värdet överstiger gränsen för skattefria reklamgåvor och produkten används privat.'
}
}
// Default: taxable if significant value
return {
taxable: input.estimatedValue > TAX_FREE_PROMO_THRESHOLD,
marketValue: input.estimatedValue,
deductibleAsExpense: input.usedInBusiness,
bookingType: input.estimatedValue > TAX_FREE_PROMO_THRESHOLD ? 'income' : 'tax_free',
reasoning: 'Klassificering baserad på värde och användning'
}
}
```
### UI Flow
1. User taps "Logga gåva/produkt"
2. Photo capture or manual entry
3. System queries price APIs (Prisjakt, Google Shopping) for value estimate
4. Decision tree questions:
- "Fanns krav på att du skulle posta om denna produkt?" [Ja/Nej]
- "Använder du produkten i din content-produktion?" [Ja/Nej]
- "Använder du produkten privat?" [Ja/Nej]
5. System shows classification + tax impact
6. Auto-booking to correct accounts
---
## Phase 2: NE-bilaga Generation
### SRU File Format
Skatteverket accepts SRU (Standardiserat RäkenskapsUtdrag) files for tax form import.
```typescript
// lib/tax/sru-generator.ts
interface NEBilaga {
// R1-series: Income
R1_nettoomsattning: number // Net revenue
R2_ovriga_intakter: number // Other income
// R3-series: Costs
R3_varor: number // Cost of goods
R4_ovriga_kostnader: number // Other expenses
R5_personal: number // Personnel costs (usually 0 for sole proprietor)
R6_avskrivningar: number // Depreciation
// R7-series: Financial
R7_ranteintakter: number
R8_rantekostnader: number
// Result
R9_resultat: number // Profit/loss before adjustments
// Adjustments
R10_periodiseringsfond_avsatt: number // Max 30% of profit
R11_periodiseringsfond_aterforing: number
R12_expansionsfond: number
// Final
R14_overskott_underskott: number
}
function generateSRUFile(data: NEBilaga, personnummer: string, year: number): string {
const lines: string[] = []
// Header
lines.push('#DATABESKRIVNING_START')
lines.push('#PRODUKT SIE')
lines.push('#FORMAT PC8')
lines.push('#FILTYP NARINGSBILAGA')
lines.push(`#UPPGIFTSLAMNARE ${personnummer}`)
lines.push(`#INKOMSTAR ${year}`)
lines.push('#DATABESKRIVNING_SLUT')
// Data rows (simplified)
lines.push(`#UPPGIFT 7001 ${data.R1_nettoomsattning}`) // R1
lines.push(`#UPPGIFT 7002 ${data.R2_ovriga_intakter}`) // R2
// ... continue for all fields
lines.push('#UPPGIFT_SLUT')
return lines.join('\n')
}
```
### BAS Account to NE Mapping
| BAS Account | Description | NE Ruta |
|-------------|-------------|---------|
| 3000-3999 | Intäkter | R1 |
| 3900-3999 | Övriga rörelseintäkter | R2 |
| 4000-4999 | Varuinköp | R3 |
| 5000-6999 | Övriga externa kostnader | R4 |
| 7000-7699 | Personalkostnader | R5 |
| 7800-7899 | Avskrivningar | R6 |
| 8300-8399 | Ränteintäkter | R7 |
| 8400-8499 | Räntekostnader | R8 |
### Tax Optimization Suggestions
Before generating NE-bilaga, system should suggest:
1. **Periodiseringsfond** (max 30% of profit)
- Defer tax to future years
- Must be reversed within 6 years
- Good for income smoothing
2. **Expansionsfond**
- Tax profit at 22% corporate rate instead of marginal personal rate
- Good if marginal rate > 22%
- More complex rules
```typescript
function suggestTaxOptimization(profit: number, marginalTaxRate: number) {
const suggestions = []
if (profit > 0) {
const maxPeriodisering = profit * 0.30
suggestions.push({
type: 'periodiseringsfond',
amount: maxPeriodisering,
taxSaved: maxPeriodisering * marginalTaxRate,
description: `Sätt av ${formatSEK(maxPeriodisering)} till periodiseringsfond för att skjuta upp ${formatSEK(maxPeriodisering * marginalTaxRate)} i skatt.`
})
}
if (profit > 100000 && marginalTaxRate > 0.30) {
suggestions.push({
type: 'expansionsfond',
description: 'Din marginalskatt är hög. Överväg expansionsfond för att beskattas med 22% istället.'
})
}
return suggestions
}
```
---
## Phase 2b: AB Salary Optimization (3:12-reglerna)
### Overview
For aktiebolag owners, the split between lön (salary) and utdelning (dividend) significantly impacts total tax. The 3:12 rules (Inkomstskattelagen kapitel 57) determine how much dividend can be taxed at the favorable 20% rate vs marginal income tax rates.
### Key Concepts
| Term | Description |
|------|-------------|
| Gränsbelopp | Annual limit for dividend taxed at 20% |
| Löneunderlag | Salary base that increases gränsbelopp |
| Kvalificerade andelar | Shares where owner is "active" in company |
| Sparat utdelningsutrymme | Unused gränsbelopp carried forward |
### Gränsbelopp Calculation (Simplified)
```typescript
// lib/tax/ab-optimization.ts
interface GransbeloppCalculation {
schablonbelopp: number // 2.75 × inkomstbasbelopp
lonebaserat: number // 50% of löneunderlag (if meets salary requirement)
sparat: number // Carried forward from previous years
totalGransbelopp: number
}
const INKOMSTBASBELOPP_2024 = 74300
function calculateGransbelopp(
ownerSalary: number,
totalCompanySalaries: number,
sparatUtdelningsutrymme: number
): GransbeloppCalculation {
// Schablonbelopp: 2.75 × IBB
const schablonbelopp = 2.75 * INKOMSTBASBELOPP_2024 // ~204k
// Lönebaserat: requires owner salary ≥ 6 IBB (or 9.6% of total + 6 IBB)
const minOwnerSalary = Math.min(
6 * INKOMSTBASBELOPP_2024,
0.096 * totalCompanySalaries + 6 * INKOMSTBASBELOPP_2024
)
const lonebaserat = ownerSalary >= minOwnerSalary
? totalCompanySalaries * 0.50
: 0
return {
schablonbelopp,
lonebaserat,
sparat: sparatUtdelningsutrymme,
totalGransbelopp: schablonbelopp + lonebaserat + sparatUtdelningsutrymme
}
}
```
### Optimization Engine
```typescript
interface OptimizationRecommendation {
recommendedSalary: number
recommendedDividend: number
taxOnSalary: number
taxOnDividend: number
totalTax: number
savingsVsAllSalary: number
explanation: string
}
function optimizeSalaryDividendSplit(
availableProfit: number,
ownerMarginalTaxRate: number,
sparatUtdelningsutrymme: number
): OptimizationRecommendation {
// Consider:
// - Minimum salary for lönebaserat gränsbelopp (6 IBB = ~446k)
// - Arbetsgivaravgifter (31.42%) on salary
// - 20% tax on dividend within gränsbelopp
// - Marginal tax on dividend above gränsbelopp
// - Bolagsskatt (20.6%) already paid on profit
// Return optimal split with explanation
}
```
### UI Concept
```
┌─────────────────────────────────────────────────────────────┐
│ 💡 OPTIMERINGSFÖRSLAG │
│ │
│ Baserat på ditt resultat och skattesituation: │
│ │
│ Rekommenderad lön: 445 800 kr/år │
│ Rekommenderad utdelning: 204 325 kr │
│ │
│ Beräknad total skatt: ~142 000 kr │
│ vs allt som lön: ~185 000 kr │
│ ───────────────────────────────────────────────────────── │
│ Potentiell besparing: ~43 000 kr │
│ │
│ ⚠️ Detta är en uppskattning. Rådgör med revisor. │
└─────────────────────────────────────────────────────────────┘
```
### Implementation Requirements
- Track sparat utdelningsutrymme (historical data)
- Input for owner's other income (affects marginal rate)
- Annual update of inkomstbasbelopp
- Strong disclaimer: Not tax advice
---
## Phase 3: Skatteverket API Integrations
### Available APIs
Skatteverket offers several APIs that can enhance the product:
#### 1. Skattekonto API
Fetch user's tax account balance and transactions.
**Prerequisite:** User must grant "Läsombud" access in Skatteverket's system.
```typescript
// Conceptual - actual implementation requires OAuth2 + organizational agreement
interface SkattekontoBala {
saldo: number
senastInbetalning: Date
kommandeForfall: Array<{
datum: Date
belopp: number
typ: string // 'F-skatt', 'Moms', etc.
}>
}
// Use case: "Du har 15 000 kr på skattekontot, men beräknad skatt är 20 000 kr. Sätt in 5 000 kr nu."
```
#### 2. Momsdeklaration API
Submit VAT declaration directly from app.
**Prerequisites:**
- Registered as e-tjänstleverantör with Skatteverket
- User authentication via BankID
- Signed agreement with Skatteverket
```typescript
interface MomsdeklarationSubmission {
period: string // '2024-01' for January
ruta05: number // Momspliktig försäljning
ruta39: number // Tjänsteförsäljning EU
ruta40: number // Export
ruta21: number // Inköp tjänster EU
ruta48: number // Utgående moms inköp
// ... all required rutor
}
```
#### 3. Arbetsgivardeklaration API
Not typically needed for sole proprietors without employees, but relevant if they hire.
### Integration Roadmap
| Phase | Integration | User Value |
|-------|-------------|------------|
| MVP | None (manual) | - |
| v1.5 | VIES VAT validation | Automated reverse charge |
| v2.0 | Skattekonto (read) | Real-time tax position |
| v2.5 | Momsdeklaration (submit) | One-click VAT filing |
| v3.0 | NE-bilaga (submit) | Full declaration automation |
---
## Phase 2: Reklammärkning Compliance
### Marknadsföringslagen Requirement
Swedish law requires clear ad disclosure. The "Kissie" case established that marking must be:
- At the very beginning of content
- Immediately visible
- Unambiguous ("Reklam" or "Annons", not just "Samarbete")
### Implementation
In Deal-to-Invoice flow, add mandatory checkbox:
```typescript
interface DealComplianceCheck {
reklamMarkerad: boolean
markeringTyp: 'inlagg' | 'video' | 'story' | 'podcast'
bekraftelse: string // Timestamp of user confirmation
}
// Before invoice can be marked as complete:
const complianceText = `Jag intygar att allt publicerat material för detta samarbete har reklammarkerats tydligt i början av varje inlägg/video i enlighet med Marknadsföringslagen (2008:486).`
```
---
## Data Retention & GDPR
### Conflict Resolution
**Bokföringslagen**: 7 years mandatory retention for räkenskapsinformation
**GDPR**: Right to erasure
Resolution:
- Accounting records (verifikationer, fakturor, kvitton): 7-year mandatory retention
- Non-accounting data (support chats, preferences, analytics): Can be deleted on request
- Technical implementation: Separate data stores with different retention policies
```typescript
// Database design consideration
interface UserDataPolicy {
accountingData: {
retention: '7_years_from_fiscal_year_end',
deletable: false,
legal_basis: 'Bokföringslagen 7 kap. 2 §'
},
operationalData: {
retention: 'until_deletion_request',
deletable: true,
legal_basis: 'Consent / Legitimate interest'
}
}
```
### Digital Archive Requirements (July 2024 Law)
Receipt photos must be:
- Stored in immutable format (PDF/A-3, locked JPEG)
- Backed up regularly
- Hosted within EU/EES or adequate country
- Tamper-evident (hash verification)
```typescript
interface ReceiptArchive {
originalFilename: string
storedAs: string // UUID.pdf
format: 'PDF/A-3'
sha256Hash: string
capturedAt: Date
linkedTransactionId: string
storageLocation: 'eu-north-1' // Must be EU
}
```
+903
View File
@@ -0,0 +1,903 @@
# BAS Accounting Guide for Creator Economy
## Overview
This document provides the accounting logic layer for automatic transaction categorization using the Swedish BAS-kontoplan. It covers MCC code mapping, expense validation rules, and VAT handling for influencer-specific transactions.
## BAS Account Structure
The BAS-kontoplan uses a hierarchical 4-digit system where each position carries semantic meaning:
| Position | Name | Example | Meaning |
|----------|------|---------|---------|
| 1 | Kontoklass | **5**410 | Category (5 = External costs) |
| 2 | Kontogrupp | 5**4**10 | Type (54 = Consumables) |
| 3 | Huvudkonto | 54**1**0 | Main account for reporting |
| 4 | Underkonto | 541**0** | Detail level, VAT codes |
This enables validation logic built directly on account number structure.
---
## Account Classes Overview
| Class | Swedish | English | Influencer Relevance |
|-------|---------|---------|---------------------|
| 1xxx | Tillgångar | Assets | Equipment, bank accounts |
| 2xxx | Eget kapital & Skulder | Equity & Liabilities | VAT, owner withdrawals |
| 3xxx | Intäkter | Revenue | Platform income, sponsorships |
| 4xxx | Direkta kostnader | Direct costs | Merchandise COGS (rare) |
| 5xxx | Övriga externa kostnader | Other external costs | 90% of expenses land here |
| 6xxx | Övriga externa kostnader | Other external costs | Services, representation |
| 7xxx | Personalkostnader | Personnel costs | AB salary (see salary module) |
| 8xxx | Finansiella poster | Financial items | Interest, currency gains/losses |
---
## Critical BAS Accounts for Influencers
### Assets (Class 1)
| Account | Name | Use Case | Notes |
|---------|------|----------|-------|
| 1210 | Maskiner och inventarier | Cameras, computers > 29,400 kr | Depreciate over 5 years (20%/year) |
| 1930 | Företagskonto | Main bank account | Primary reconciliation account |
### Equity & Liabilities (Class 2)
| Account | Name | Use Case | Notes |
|---------|------|----------|-------|
| 2010 | Eget kapital | Owner's equity | Rolling balance |
| 2013 | Eget uttag | Private expenses paid with business card | **Critical for rejected deductions** |
| 2018 | Egen insättning | Owner deposits / travel allowance credit | Traktamente booking |
| 2440 | Leverantörsskulder | Accounts payable | If not paying immediately |
| 2611 | Utgående moms 25% | Output VAT 25% | Swedish domestic sales |
| 2614 | Utg. moms utländska förvärv | Output VAT on foreign purchases | Reverse charge (credit side) |
| 2641 | Ingående moms | Input VAT | Deductible VAT on purchases |
| 2645 | Ing. moms utländska förvärv | Input VAT on foreign purchases | Reverse charge (debit side) |
### Revenue (Class 3)
| Account | Name | Use Case | Notes |
|---------|------|----------|-------|
| 3001 | Försäljning 25% | Swedish sponsorships/collaborations | Standard domestic |
| 3044 | Försäljning tjänst EU | AdSense (Ireland), EU affiliates | VAT-free, requires Periodisk sammanställning |
| 3045 | Försäljning tjänst Export | Twitch (USA), non-EU platforms | VAT-free (export) |
| 3900 | Övriga intäkter | Stipends, royalties, misc | |
### Costs (Class 5-6)
| Account | Name | Use Case | Notes |
|---------|------|----------|-------|
| 5010 | Lokalhyra | Studio rental | Not for home office (strict rules) |
| 5410 | Förbrukningsinventarier | Cameras, computers < 29,400 kr | Direct deduction |
| 5420 | Programvaror | Adobe CC, Epidemic Sound, SaaS | Licenses/subscriptions |
| 5480 | Arbetskläder | Protective gear, uniforms | **NOT regular clothes** |
| 5490 | Övriga förbrukningsartiklar | Props, consumable supplies | |
| 5800 | Resekostnader | Train, flights, car rental | VAT varies (6% domestic travel) |
| 5820 | Taxikostnader | Taxi, Uber | |
| 5831 | Kost och logi Sverige | Hotels in Sweden | 12% VAT |
| 5832 | Kost och logi utlandet | Hotels abroad | No VAT recovery |
| 5841 | Traktamente, avdragsgillt | Travel allowance | Calculated per Skatteverket rates |
| 5890 | Övriga resekostnader | Non-deductible travel portion | For mixed-use trips |
| 5910 | Annonsering | Facebook Ads, Google Ads | Often triggers reverse charge |
| 5930 | Reklamtrycksaker | Promotional materials | |
| 6071 | Representation, avdragsgill | Simple refreshments (max 60 kr/person) | Very limited deduction |
| 6072 | Representation, ej avdragsgill | Meals for representation | VAT still deductible |
| 6230 | Datakommunikation | Web hosting, domains, internet | Infrastructure |
| 6500 | Övriga externa tjänster | Photographers, editors (with F-skatt) | Consultant services |
| 6991 | Övriga avdragsgilla kostnader | Miscellaneous deductible | Use sparingly |
| 6992 | Övriga ej avdragsgilla | Fines, penalties | No tax effect |
---
## MCC Code Mapping
Merchant Category Codes (MCC) are the primary signal for automatic categorization from bank transactions.
### Implementation
```typescript
// lib/accounting/mcc-mapping.ts
interface MCCMapping {
mcc: string
description: string
riskLevel: 'low' | 'medium' | 'high' | 'very_high'
primaryAccount: string
secondaryAccount?: string
requiresUserInput: boolean
autoRejectReason?: string
logic: string
}
const MCC_MAPPINGS: MCCMapping[] = [
// ADVERTISING & MARKETING
{
mcc: '7311',
description: 'Advertising Services',
riskLevel: 'low',
primaryAccount: '5910',
secondaryAccount: '5930',
requiresUserInput: false,
logic: 'Standard for Facebook/Google Ads. Triggers EU VAT check.'
},
// ELECTRONICS & EQUIPMENT
{
mcc: '5732',
description: 'Electronics Stores',
riskLevel: 'medium',
primaryAccount: '5410',
secondaryAccount: '1210',
requiresUserInput: false,
logic: 'Amount determines routing. >29,400 kr → 1210 (asset).'
},
{
mcc: '5946',
description: 'Camera & Photo Supply',
riskLevel: 'medium',
primaryAccount: '5410',
secondaryAccount: '1210',
requiresUserInput: false,
logic: 'Same as electronics. Check amount threshold.'
},
// RESTAURANTS & FOOD - HIGH RISK
{
mcc: '5812',
description: 'Eating Places/Restaurants',
riskLevel: 'high',
primaryAccount: '6072',
secondaryAccount: '2013',
requiresUserInput: true,
logic: 'Requires user input: Representation or private? Default to private (2013).'
},
{
mcc: '5814',
description: 'Fast Food',
riskLevel: 'high',
primaryAccount: '2013',
requiresUserInput: false,
autoRejectReason: 'Fast food is typically private expense',
logic: 'Default to eget uttag. Rarely valid business expense.'
},
// CLOTHING - VERY HIGH RISK (Almost always rejected)
{
mcc: '5611',
description: "Men's Clothing",
riskLevel: 'very_high',
primaryAccount: '2013',
secondaryAccount: '5480',
requiresUserInput: true,
autoRejectReason: 'Clothes that can be worn privately are not deductible (RÅ81 1:29)',
logic: 'Default MUST be 2013 (private). Only 5480 if protective/uniform.'
},
{
mcc: '5621',
description: "Women's Ready-to-Wear",
riskLevel: 'very_high',
primaryAccount: '2013',
secondaryAccount: '5480',
requiresUserInput: true,
autoRejectReason: 'Clothes that can be worn privately are not deductible',
logic: 'Default MUST be 2013 (private).'
},
{
mcc: '5631',
description: "Women's Accessories",
riskLevel: 'very_high',
primaryAccount: '2013',
requiresUserInput: false,
autoRejectReason: 'Accessories are private expenses',
logic: 'Always 2013.'
},
// COSMETICS - VERY HIGH RISK
{
mcc: '5977',
description: 'Cosmetic Stores',
riskLevel: 'very_high',
primaryAccount: '2013',
secondaryAccount: '5490',
requiresUserInput: true,
autoRejectReason: 'Cosmetics are private expenses unless specific props',
logic: 'Default private (2013). Only 5490 if clearly production props.'
},
// TRAVEL - MEDIUM RISK
{
mcc: '4111',
description: 'Commuter Transport',
riskLevel: 'low',
primaryAccount: '5800',
requiresUserInput: false,
logic: 'Train/bus. 6% VAT in Sweden.'
},
{
mcc: '4112',
description: 'Passenger Railways',
riskLevel: 'low',
primaryAccount: '5800',
requiresUserInput: false,
logic: 'Train tickets. 6% VAT.'
},
{
mcc: '4121',
description: 'Taxicabs/Limousines',
riskLevel: 'medium',
primaryAccount: '5820',
secondaryAccount: '2013',
requiresUserInput: true,
logic: 'Business trip or private? Ask user.'
},
{
mcc: '4722',
description: 'Travel Agencies',
riskLevel: 'medium',
primaryAccount: '5800',
secondaryAccount: '2013',
requiresUserInput: true,
logic: 'Is the trip business-related? May need to split.'
},
{
mcc: '7011',
description: 'Hotels/Motels',
riskLevel: 'medium',
primaryAccount: '5831',
secondaryAccount: '5832',
requiresUserInput: false,
logic: '5831 for Sweden (12% VAT), 5832 for abroad (no VAT).'
},
// SOFTWARE & DIGITAL SERVICES - LOW RISK
{
mcc: '7372',
description: 'Computer Programming',
riskLevel: 'low',
primaryAccount: '6230',
secondaryAccount: '5420',
requiresUserInput: false,
logic: 'Web hosting, domains, SaaS subscriptions.'
},
{
mcc: '5734',
description: 'Computer Software Stores',
riskLevel: 'low',
primaryAccount: '5420',
requiresUserInput: false,
logic: 'Software licenses.'
},
// ALCOHOL - ALWAYS REJECT
{
mcc: '5921',
description: 'Package Stores (Systembolaget)',
riskLevel: 'very_high',
primaryAccount: '2013',
requiresUserInput: false,
autoRejectReason: 'Alcohol is not deductible',
logic: 'Always 2013. No exceptions.'
},
// GROCERIES - HIGH RISK
{
mcc: '5411',
description: 'Grocery Stores',
riskLevel: 'high',
primaryAccount: '2013',
secondaryAccount: '6072',
requiresUserInput: true,
autoRejectReason: 'Food is typically private expense',
logic: 'Default 2013. Only 6072 if documented representation.'
},
// STREAMING SERVICES
{
mcc: '4899',
description: 'Cable/Streaming Services',
riskLevel: 'medium',
primaryAccount: '5420',
secondaryAccount: '2013',
requiresUserInput: true,
logic: 'Netflix for research? Usually private. Ask user.'
},
// GYM & FITNESS - ALWAYS REJECT
{
mcc: '7941',
description: 'Sports Clubs/Gyms',
riskLevel: 'very_high',
primaryAccount: '2013',
requiresUserInput: false,
autoRejectReason: 'Gym memberships are private expenses, even for fitness influencers',
logic: 'Always 2013. IL 9 kap. 2 §'
}
]
```
---
## Keyword-Based Categorization
For generic MCCs (Amazon, PayPal), analyze transaction description text.
```typescript
// lib/accounting/keyword-rules.ts
interface KeywordRule {
keywords: string[]
account: string
confidence: 'high' | 'medium' | 'low'
vatTreatment?: 'standard' | 'reverse_charge' | 'none'
}
const KEYWORD_RULES: KeywordRule[] = [
// Software & Subscriptions
{
keywords: ['adobe', 'creative cloud'],
account: '5420',
confidence: 'high',
vatTreatment: 'reverse_charge' // Adobe Ireland
},
{
keywords: ['epidemic sound'],
account: '5420',
confidence: 'high',
vatTreatment: 'standard' // Swedish company
},
{
keywords: ['canva'],
account: '5420',
confidence: 'high',
vatTreatment: 'reverse_charge'
},
{
keywords: ['spotify', 'spotify ab'],
account: '2013', // Usually private
confidence: 'medium'
},
{
keywords: ['spotify for business', 'soundtrack'],
account: '5420',
confidence: 'high'
},
// Web Infrastructure
{
keywords: ['one.com', 'loopia', 'godaddy', 'namecheap', 'cloudflare'],
account: '6230',
confidence: 'high'
},
{
keywords: ['vercel', 'netlify', 'heroku', 'aws', 'digitalocean'],
account: '6230',
confidence: 'high',
vatTreatment: 'reverse_charge'
},
// Advertising Platforms
{
keywords: ['facebook', 'meta', 'instagram ads', 'fb ads'],
account: '5910',
confidence: 'high',
vatTreatment: 'reverse_charge' // Meta Ireland
},
{
keywords: ['google ads', 'adwords'],
account: '5910',
confidence: 'high',
vatTreatment: 'reverse_charge' // Google Ireland
},
{
keywords: ['tiktok ads', 'tiktok for business'],
account: '5910',
confidence: 'high',
vatTreatment: 'reverse_charge'
},
// Transport
{
keywords: ['uber', 'bolt', 'taxi'],
account: '5820',
confidence: 'medium' // Could be private
},
{
keywords: ['sj', 'mtrx', 'sj biljett'],
account: '5800',
confidence: 'high'
},
{
keywords: ['sas', 'norwegian', 'ryanair', 'flygbiljett'],
account: '5800',
confidence: 'medium' // Could be private trip
},
// Always Private
{
keywords: ['systembolaget'],
account: '2013',
confidence: 'high'
},
{
keywords: ['apotek', 'apoteket', 'apotea'],
account: '2013',
confidence: 'high'
},
{
keywords: ['ica', 'coop', 'willys', 'hemköp', 'lidl'],
account: '2013',
confidence: 'medium' // Usually private groceries
}
]
```
---
## Amount-Based Routing (Asset vs Expense)
```typescript
// lib/accounting/asset-routing.ts
const PRISBASBELOPP_2025 = 58800
const HALF_PBB = PRISBASBELOPP_2025 / 2 // 29,400 kr
interface AssetDecision {
shouldCapitalize: boolean
account: string
depreciationYears?: number
depreciationRate?: number
}
function routeEquipmentPurchase(
amountExVat: number,
mccCode: string
): AssetDecision {
const isEquipmentMCC = ['5732', '5946', '5045'].includes(mccCode)
if (!isEquipmentMCC) {
return { shouldCapitalize: false, account: '5410' }
}
if (amountExVat > HALF_PBB) {
return {
shouldCapitalize: true,
account: '1210',
depreciationYears: 5,
depreciationRate: 0.20
}
}
return {
shouldCapitalize: false,
account: '5410'
}
}
// Update HALF_PBB annually - Skatteverket publishes in November
```
---
## Reverse Charge VAT Handling
When purchasing services from foreign (usually EU) companies, Swedish businesses must self-report VAT.
### Detection Logic
```typescript
// lib/accounting/reverse-charge.ts
interface ReverseChargeResult {
applies: boolean
reason: string
bookings: Array<{
account: string
debit?: number
credit?: number
description: string
}>
}
const EU_DIGITAL_SERVICES_VENDORS = [
{ name: 'meta', country: 'IE', keywords: ['facebook', 'meta', 'instagram'] },
{ name: 'google', country: 'IE', keywords: ['google', 'youtube', 'adwords'] },
{ name: 'adobe', country: 'IE', keywords: ['adobe'] },
{ name: 'microsoft', country: 'IE', keywords: ['microsoft', 'office 365', 'azure'] },
{ name: 'amazon', country: 'LU', keywords: ['aws', 'amazon web services'] },
{ name: 'canva', country: 'AU', keywords: ['canva'] }, // Non-EU, different treatment
]
function handleForeignPurchase(
amount: number,
vendorName: string,
vendorCountry: string,
expenseAccount: string
): ReverseChargeResult {
const isEU = isEUCountry(vendorCountry)
const vatRate = 0.25
const vatAmount = amount * vatRate
if (isEU && vendorCountry !== 'SE') {
// EU purchase - reverse charge applies
return {
applies: true,
reason: `Omvänd skattskyldighet - inköp från ${vendorCountry}`,
bookings: [
{ account: expenseAccount, debit: amount, description: 'Kostnad' },
{ account: '2645', debit: vatAmount, description: 'Beräknad ing. moms utl. förvärv' },
{ account: '2614', credit: vatAmount, description: 'Utg. moms utl. förvärv' },
{ account: '1930', credit: amount, description: 'Utbetalning' }
]
}
}
if (!isEU) {
// Non-EU purchase - no reverse charge, just expense
return {
applies: false,
reason: 'Import från land utanför EU',
bookings: [
{ account: expenseAccount, debit: amount, description: 'Kostnad' },
{ account: '1930', credit: amount, description: 'Utbetalning' }
]
}
}
// Swedish domestic
return {
applies: false,
reason: 'Inrikes inköp',
bookings: [] // Normal VAT handling
}
}
```
### Momsdeklaration Mapping
| Scenario | Ruta | Description |
|----------|------|-------------|
| Domestic purchase 25% VAT | 48 (utgående), deduct in 48 | Normal |
| EU service purchase (reverse charge) | 21 (purchase amount), 48 (calculated VAT) | Self-report |
| Non-EU service purchase | No VAT reporting | Just expense |
---
## Revenue Account Selection
```typescript
// lib/accounting/revenue-routing.ts
interface RevenueRouting {
account: string
vatRate: number
momsRuta: number
requiresPeriodiskSammanstallning: boolean
}
function routeRevenue(
customerCountry: string,
customerType: 'business' | 'consumer',
hasValidVatNumber: boolean
): RevenueRouting {
// Swedish customer
if (customerCountry === 'SE') {
return {
account: '3001', // Försäljning 25%
vatRate: 25,
momsRuta: 5,
requiresPeriodiskSammanstallning: false
}
}
// EU business with VAT number
if (isEUCountry(customerCountry) && customerType === 'business' && hasValidVatNumber) {
return {
account: '3044', // Försäljning tjänst EU
vatRate: 0,
momsRuta: 39,
requiresPeriodiskSammanstallning: true // Quarterly report required!
}
}
// EU consumer (OSS rules may apply if >10k EUR/year)
if (isEUCountry(customerCountry) && customerType === 'consumer') {
return {
account: '3001', // Or OSS-specific account
vatRate: 25, // Swedish VAT until OSS threshold
momsRuta: 5,
requiresPeriodiskSammanstallning: false
}
}
// Non-EU (USA, etc.) - Export
return {
account: '3045', // Försäljning tjänst Export
vatRate: 0,
momsRuta: 40,
requiresPeriodiskSammanstallning: false
}
}
```
---
## Conservative Bias Principle
The system must be designed with a **conservative bias** - prefer rejecting deductions over accepting questionable ones.
### Rationale
- Burden of proof lies with taxpayer (IL 9 kap. 2 §)
- Skattetillägg (tax surcharge) is 40% of avoided tax
- Better to warn user than cause audit problems
### Implementation
```typescript
// lib/accounting/risk-assessment.ts
interface RiskAssessment {
riskLevel: 'low' | 'medium' | 'high' | 'very_high'
suggestedAccount: string
requiresConfirmation: boolean
warningMessage?: string
legalBasis?: string
}
function assessTransactionRisk(
mcc: string,
amount: number,
description: string
): RiskAssessment {
const mapping = getMCCMapping(mcc)
if (mapping.riskLevel === 'very_high') {
return {
riskLevel: 'very_high',
suggestedAccount: '2013', // Default to private (eget uttag)
requiresConfirmation: true,
warningMessage: mapping.autoRejectReason,
legalBasis: 'IL 9 kap. 2 § - Privata levnadskostnader'
}
}
if (mapping.riskLevel === 'high') {
return {
riskLevel: 'high',
suggestedAccount: mapping.primaryAccount,
requiresConfirmation: true,
warningMessage: `Denna typ av utgift kräver dokumentation. ${mapping.logic}`
}
}
// Low/medium risk - auto-categorize but allow override
return {
riskLevel: mapping.riskLevel,
suggestedAccount: mapping.primaryAccount,
requiresConfirmation: false
}
}
```
---
## Private Use Split
For items with mixed business/private use:
```typescript
// lib/accounting/split-usage.ts
interface SplitTransaction {
businessPortion: {
account: string
amount: number
}
privatePortion: {
account: string // Always 2013
amount: number
}
}
function splitMixedUse(
totalAmount: number,
businessPercentage: number,
expenseAccount: string
): SplitTransaction {
const businessAmount = totalAmount * (businessPercentage / 100)
const privateAmount = totalAmount - businessAmount
return {
businessPortion: {
account: expenseAccount,
amount: Math.round(businessAmount * 100) / 100
},
privatePortion: {
account: '2013',
amount: Math.round(privateAmount * 100) / 100
}
}
}
// UI should ask: "Används denna uteslutande i företaget?"
// If no → prompt for percentage split
```
---
## Travel Allowance (Traktamente)
For business trips, the owner can claim tax-free travel allowance at Skatteverket's rates.
```typescript
// lib/accounting/traktamente.ts
// 2024 rates - update annually
const TRAKTAMENTE_RATES = {
sweden: {
fullDay: 260, // Hel dag
halfDay: 130, // Halv dag
night: 130 // Nattraktamente
},
// Foreign rates vary by country - see Skatteverket's list
foreign: {
USA: { fullDay: 620, reduced: 434 },
UK: { fullDay: 550, reduced: 385 },
Germany: { fullDay: 420, reduced: 294 },
// ... etc
}
}
interface TraktamenteCalculation {
amount: number
bookings: Array<{
account: string
debit?: number
credit?: number
}>
}
function calculateTraktamente(
destination: 'sweden' | string,
fullDays: number,
halfDays: number,
nights: number
): TraktamenteCalculation {
const rates = destination === 'sweden'
? TRAKTAMENTE_RATES.sweden
: TRAKTAMENTE_RATES.foreign[destination] || TRAKTAMENTE_RATES.sweden
const amount =
(fullDays * rates.fullDay) +
(halfDays * (rates.halfDay || rates.fullDay / 2)) +
(nights * (rates.night || 0))
return {
amount,
bookings: [
{ account: '5841', debit: amount }, // Traktamente, avdragsgillt
{ account: '2018', credit: amount } // Egen insättning (skuld till ägare)
]
}
}
```
---
## Non-Cash Transactions (Barter/Gifts)
Influencers receive products that must be accounted for even without bank transaction.
```typescript
// lib/accounting/non-cash.ts
interface BarterTransaction {
productValue: number
hasMotprestation: boolean // Required post/exposure?
usedInBusiness: boolean
bookings: Array<{
account: string
debit?: number
credit?: number
description: string
}>
}
function bookBarterTransaction(
productValue: number,
hasMotprestation: boolean,
usedInBusiness: boolean
): BarterTransaction['bookings'] {
if (!hasMotprestation && productValue < 450) {
// Tax-free promotional gift
return [] // No booking required
}
if (hasMotprestation) {
// Taxable barter - must recognize as income
const bookings = [
{ account: '3001', credit: productValue, description: 'Förmån/Byteshandel' },
{ account: '1510', debit: productValue, description: 'Fordran (kvittad mot produkt)' }
]
if (usedInBusiness) {
// Can also expense it
bookings.push(
{ account: '5490', debit: productValue, description: 'Rekvisita' },
{ account: '1510', credit: productValue, description: 'Kvittning fordran' }
)
} else {
// Private use - eget uttag
bookings.push(
{ account: '2013', debit: productValue, description: 'Eget uttag (förmån)' },
{ account: '1510', credit: productValue, description: 'Kvittning fordran' }
)
}
return bookings
}
// Gift without motprestation but valuable - still taxable
return [
{ account: '3001', credit: productValue, description: 'Skattepliktig gåva' },
{ account: '2013', debit: productValue, description: 'Eget uttag (gåva)' }
]
}
```
---
## System Configuration
```typescript
// lib/accounting/config.ts
export const ACCOUNTING_CONFIG = {
// Update annually
prisbasbelopp: 58800, // 2025
halfPrisbasbelopp: 29400,
// VAT rates
vatRates: {
standard: 25,
reduced: 12, // Food, hotels
low: 6, // Transport, books
},
// Depreciation defaults
depreciation: {
equipment: { years: 5, rate: 0.20 },
computers: { years: 3, rate: 0.33 },
vehicles: { years: 5, rate: 0.20 },
},
// Risk thresholds
riskSettings: {
autoRejectVeryHigh: true,
requireConfirmationHigh: true,
allowOverrideMedium: true,
}
}
```
---
## Integration Points
This guide integrates with:
1. **Transaction categorization UI** (05-UI-SPECIFICATION.md) - Swipe interface uses MCC mapping
2. **Tax calculator** (06-IMPLEMENTATION-GUIDE.md) - Uses account classifications for tax estimation
3. **Invoice VAT rules** (04-API-SPECIFICATION.md) - Revenue account selection aligns with momsdeklaration
4. **Expense warnings** (06-IMPLEMENTATION-GUIDE.md) - Risk levels drive warning display
---
## Annual Maintenance
Update each January:
1. Prisbasbelopp (from Skatteverket, published November)
2. Traktamente rates (Skatteverket)
3. Any BAS account changes (BAS-kontogruppen)
4. Tax rates if changed by Riksdag
File diff suppressed because one or more lines are too long
+940
View File
@@ -0,0 +1,940 @@
API reference
Scroll down for example requests and responses.
Base URLs:
https://api.enablebanking.com
https://api.tilisy.com (deprecated)
Flow diagrams
Account information flow
AIS flow diagram
Application (i.e. API client) makes GET /aspsps request to obtain a list of available ASPSPs along with necessary meta data. Alternatively, the list of ASPSP can be displayed using the ASPSP selection UI widget.
List of available ASPSPs is returned and displayed to a PSU.
The PSU selects desired ASPSP and an application makes POST /auth request, specifying desired ASPSP and providing information about needed access rights.
Enable Banking starts authorisation in a desired ASPSP.
Enable Banking responds to the client with a redirect url to a Enable Banking page, where PSU needs to be redirected.
The PSU is redirected to the Enable Banking page.
After the PSU is redirected, Enable Banking does interactions with an ASPSP necessary to get authorised access to the PSU's account.
These actions are ASPSP-specific and may be different depending of the authentication method (which may be specified at step 3).
The PSU is redirected to the callback URL provided by the application with additional parameters added in its query string.
If the authorisation went successfully then query string from step 8 will contain code parameter, which needs to be sent in POST /sessions request.
The Enable Banking API will respond with created session_id along with a list of accessible accounts and their details.
Note that some of the information returned in that call is shown only once.
After successfull response to POST /sessions request the application can start making requests to Enable Banking API to fetch information about session, account balances and transactions.
Possible query parameters returned in the step 8 (parameters follow The OAuth 2.0 Authorization Framework (opens new window)):
code — authorisation code.
state — same as state, provided in the step 1.
error — error code
error_description — human-readable error description
Possible error descriptions:
Denied data sharing consent — user cancelled authentication before accepting data sharing consent (error code is access_denied)
Cancelled by user — user cancelled authorisation of access to account information (error code is access_denied). There are also arbitrary error descriptions possible, which are coming from ASPSPs.
Payment initiation flow
PIS flow diagram
Application (i.e. API client) makes GET /aspsps request to obtain a list of available ASPSPs along with necessary meta data. Alternatively, the list of ASPSP can be displayed using the ASPSP selection UI widget.
List of available ASPSPs is returned and displayed to a PSU.
The PSU selects desired ASPSP and the application makes POST /payments request, specifying a desired ASPSP, providing details for the payment to be initiated and other details such as callback URL, preferred authentication method, etc.
Enable Banking responds to the application with an ID assigned to the payment and a URL of the page, where PSU needs to be redirected.
The PSU is redirected to the Enable Banking page, where they shall review payment details and terms of the service.
After the PSU accepted term of service, Enable Banking does interactions with the ASPSP necessary to initiate the payment and complete its authorisation.
These actions are ASPSP-specific and may be different depending of the authentication method (which may be specified at step 3).
The PSU is redirected to the callback URL provided by the application with additional parameters added in its query string.
Possible query parameters returned in the step 7:
state — same as state, provided in the step 1.
error — error code
error_description — human-readable error description
Possible error descriptions:
Cancelled by user — user cancelled authorisation of the payment (error code is access_denied). There are also arbitrary error descriptions possible, which are coming from ASPSPs.
Authentication
In order to get access to this API you need to:
Generate a private RSA key and a self-signed certificate;
Upload the certificate to enablebanking.com and get application ID;
Construct JWT with the data described below and signed with your private key;
Send the JWT in the Authorization header.
Private key and certificate generation
Generating private RSA key
openssl genrsa -out private.key 4096
OpenSSL CLI can be used for generation of a private key and self-signed certificate.
Make sure you keep the private key in secret (e.g. don't expose it to client, share with anyone nor embed into mobile or other apps intalled to user devices).
Generating self-signed certificate
openssl req -new -x509 -days 365 -key private.key -out public.crt -subj "/C=FI/ST=Uusima/L=Helsinki/O=ExampleOrganisation/CN=www.bigorg.com"
You should replace values under -subj with appropriate values.
Alternatively you can use the private key generated in your browser when registering a new application. Just choose Generate in the browser (using SubtleCrypto) and export private key option when registering an application, and the private key will be exported after the application has been registered (the corresponding certificate will be used for the app registration).
Certificate upload and application registration
To register a new application you need to have an account on the Enable Banking Control Panel (opens new window). You can create one by visiting https://enablebanking.com/sign-in/ (opens new window)and entering your email address (a one-time authentication link will be sent to your email address).
In the app registration form (opens new window)you will be asked to upload the public certificate that you created for the application being registered.
An application can be registered to either PRODUCTION (aka "live") or SANDBOX (aka "simulation") environment. Applications can not be transferred from the sandbox to the production environment and vice versa.
Applications registered into the sandbox environment are activated automatically. Applications registered to the production environment at first appear as pending and will be activated either after contractual formalities for the use of the API are cleared or after you whitelist your own accounts. For more information please contact us at info@enablebanking.com.
Application registration API
You can also register an application sending POST request containing JSON with the application details and public certificate to https://enablebanking.com/api/applications endpoint.
The JSON body for the endpoint is to include the following fields:
"certificate": Content of the certificate or public key of the application (always required)
"environment": Environment (SANDBOX or PRODUCTION) in which the application will operate (always required)
"name": Name of the application being registered (always required)
"redirect_urls": List of allowed redirect URLs for the application (always required)
"description": Description of the application being registered (required when the environment field is set to PRODUCTION)
"gdpr_email": Email address for data protection matters (required when the environment field is set to PRODUCTION)
"privacy_url": URL of the application's privacy policy (required when the environment field is set to PRODUCTION)
"terms_url": URL of the application's terms of service (required when the environment field is set to PRODUCTION)
App registration example using curl
curl -X POST -H "Authorization: Bearer YOUR-JWT-ON-ENABLEBANKING-COM" \
-H "Content-Type: application/json" \
-d "{\"name\":\"My app\",\"certificate\":\"$(cat public.crt | tr '\n' '|' | sed 's/|/\\n/g')\",\"environment\":\"SANDBOX\",\"redirect_urls\":[\"https://example.org/\"]}" \
https://enablebanking.com/api/applications
In response to the app registration request, you will receive an ID assigned to your application, which is to be used when forming JTW token.
Example response
{
"app_id": "cf589be3-3755-465b-a8df-a90a16a31403"
}
JWT format and signature
JWT example
eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiY2Y1ODliZTMtMzc1NS00NjViLWE4ZGYtYTkwYTE2YTMxNDAzIn0.eyJpc3MiOiAiZW5hYmxlYmFua2luZy5jb20iLCAiYXVkIjogImFwaS50aWxpc3kuY29tIiwgImlhdCI6IDE2MDE0NTY3NjgsICJleHAiOiAxNjAxNTQzMTY4fQ.daO3ENSYIA3ud7Ay7uGQ0xxqq9r4_WLcM5SbrN_6_fqsFZXFdoGQA5nKiyP8Ot4nWdYcZvaNWxEAOIodUFndOP8pjihF9-rMXuNGEjde1cq2WjYzKwiIeodUej8okDWdB--szcgurzGMd8RRMjqr951PWqnXS-PbrRsavDHp8l2q4YBjh2m80nRruKnQCAn0dtm4A5G9rZaEowo9z-c8HJU101jKddyOpHhl9UvxVrERzHtyO4LdidiP4rP1hmaVMWybSbcIMI_h30qjqWP21kYRH9ENITTttbf0uZIa8s74jKYxNIdiiDyRaq9WjoPolrHI_ZxcMjp8mmCKX-N-1w
You can read more about JWT here: https://jwt.io/introduction/
JWT header must contain following fields:
"typ": "JWT" (always the same)
"alg": "RS256" (always the same, only RS256 is supported)
"kid": "<application_id>" (application id obtained after certificate upload)
JWT body must contain following fields:
"iss": "enablebanking.com" (always the same)
"aud": "api.enablebanking.com" (always the same, formerly had to be "api.tilisy.com", which is now deprecated)
"iat": 1601456603 (timestamp when the token is being created)
"exp": 1601460262 (timestamp when the token expires)
Maximum allowed time-to-live for token is 86400 seconds (24 hours). Tokens created with longer TTL are not accepted by the API.
Check code samples in C#, Node.js, PHP, Python and Ruby in our GitHub repository(opens new window)
https://github.com/enablebanking/enablebanking-api-samples
Send request with JWT provided
Example request
GET https://api.enablebanking.com/application HTTP/1.1
Host: api.enablebanking.com
Authorization: Bearer eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJSUzI1NiIsICJraWQiOiAiY2Y1ODliZTMtMzc1NS00NjViLWE4ZGYtYTkwYTE2YTMxNDAzIn0.eyJpc3MiOiAiZW5hYmxlYmFua2luZy5jb20iLCAiYXVkIjogImFwaS5lbmFibGViYW5raW5nLmNvbSIsICJpYXQiOiAxNjAxNDU2NzY4LCAiZXhwIjogMTYwMTU0MzE2OH0.daO3ENSYIA3ud7Ay7uGQ0xxqq9r4_WLcM5SbrN_6_fqsFZXFdoGQA5nKiyP8Ot4nWdYcZvaNWxEAOIodUFndOP8pjihF9-rMXuNGEjde1cq2WjYzKwiIeodUej8okDWdB--szcgurzGMd8RRMjqr951PWqnXS-PbrRsavDHp8l2q4YBjh2m80nRruKnQCAn0dtm4A5G9rZaEowo9z-c8HJU101jKddyOpHhl9UvxVrERzHtyO4LdidiP4rP1hmaVMWybSbcIMI_h30qjqWP21kYRH9ENITTttbf0uZIa8s74jKYxNIdiiDyRaq9WjoPolrHI_ZxcMjp8mmCKX-N-1w
In order to authenticate your application, you need to provide JWT in the "Authorization" header of your request.
User sessions
The following operations can be used to initiate and complete end-user authorization for access to account information. The other operations provide possibility to retrieve session status and other details and to close (delete) a session.
Start user authorization
POST /auth
Start authorization by getting a redirect link and redirecting a PSU to that link
Parameters
Name In Type Required Description
body body StartAuthorizationRequest true none
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
POST https://api.enablebanking.com/auth HTTP/1.1
Host: api.enablebanking.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <JWT>
Request body
{
"access": {
"valid_until": "2019-08-24T14:15:22Z"
},
"aspsp": {
"name": "Nordea",
"country": "FI"
},
"state": "3a57e2d3-2e0c-4336-af9b-7fa94f0606a3",
"redirect_url": "http://example.com",
"psu_type": "business",
"auth_method": "methodName",
"credentials": {
"userId": "MyUsername"
},
"credentials_autosubmit": true,
"language": "fi",
"psu_id": "string"
}
Responses
Status Description Schema
200 Successful Response StartAuthorizationResponse
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"url": "https://tilisy.enablebanking.com/welcome?sessionid=73100c65-c54d-46a1-87d1-aa3effde435a",
"authorization_id": "73100c65-c54d-46a1-87d1-aa3effde435a",
"psu_id_hash": "string"
}
Authorize user session
POST /sessions
Authorize user session by provided authorization code
Parameters
Name In Type Required Description
body body AuthorizeSessionRequest true none
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
POST https://api.enablebanking.com/sessions HTTP/1.1
Host: api.enablebanking.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer <JWT>
Request body
{
"code": "string"
}
Responses
Status Description Schema
200 Successful Response AuthorizeSessionResponse
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"session_id": "string",
"accounts": [
{
"account_id": {
"iban": "FI0455231152453547"
},
"all_account_ids": [
{
"identification": "123456",
"scheme_name": "BBAN"
}
],
"account_servicer": {
"bic_fi": "string",
"clearing_system_member_id": {
"clearing_system_id": "NZNCC",
"member_id": 20368
},
"name": "string"
},
"name": "string",
"details": "string",
"usage": "PRIV",
"cash_account_type": "CACC",
"product": "string",
"currency": "string",
"psu_status": "string",
"credit_limit": {
"currency": "EUR",
"amount": "1.23"
},
"legal_age": true,
"postal_address": {
"address_type": "Business",
"department": "Department of resources",
"sub_department": "Sub Department of resources",
"street_name": "Vasavagen",
"building_number": "4",
"post_code": "00123",
"town_name": "Helsinki",
"country_sub_division": "Uusimaa",
"country": "FI",
"address_line": [
"Mr Asko Teirila PO Box 511",
"39140 AKDENMAA FINLAND"
]
},
"uid": "07cc67f4-45d6-494b-adac-09b5cbc7e2b5",
"identification_hash": "WwpbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoiaWJhbiIKXQpd.E8GzhnnsFC7K+4e3YMYYKpyM83Zx6toXrjgcvPP/Lqc=",
"identification_hashes": [
"WwpbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoiaWJhbiIKXQpd.E8GzhnnsFC7K+4e3YMYYKpyM83Zx6toXrjgcvPP/Lqc=",
"WwpbCiJhc3BzcF9uYW1lIgpdLApbCiJhc3BzcF9jb3VudHJ5IgpdLApbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoib3RoZXIiLAoic2NoZW1lX25hbWUiCl0sClsKImFjY291bnQiLAoiYWNjb3VudF9pZCIsCiJvdGhlciIsCiJpZGVudGlmaWNhdGlvbiIKXQpd.AOm/TULGPD4a4GdcWhR9xh0GPlPUZuB2O1S9SYFWEz0="
]
}
],
"aspsp": {
"name": "Nordea",
"country": "FI"
},
"psu_type": "business",
"access": {
"valid_until": "2019-08-24T14:15:22Z"
}
}
Get session data
GET /sessions/{session_id}
Get session data by session ID
Parameters
Name In Type Required Description
session_id path string(uuid) true Previously authorized session ID
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
GET https://api.enablebanking.com/sessions/{session_id} HTTP/1.1
Host: api.enablebanking.com
Accept: application/json
Authorization: Bearer <JWT>
Responses
Status Description Schema
200 Successful Response GetSessionResponse
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"access": {
"valid_until": "2020-12-01T12:00:00.000000+00:00"
},
"accounts": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
],
"accounts_data": [
{
"identification_hash": "WwpbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoiaWJhbiIKXQpd.E8GzhnnsFC7K+4e3YMYYKpyM83Zx6toXrjgcvPP/Lqc=",
"uid": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
}
],
"aspsp": {
"country": "FI",
"name": "Nordea"
},
"authorized": "2020-12-01T12:00:00.000000+00:00",
"created": "2020-12-01T12:00:00.000000+00:00",
"psu_type": "business",
"status": "AUTHORIZED"
}
Delete session
DELETE /sessions/{session_id}
Delete session by session ID. PSU's bank consent will be closed automatically if possible
Parameters
Name In Type Required Description
session_id path string(uuid) true Previously authorized session ID
Psu-Ip-Address header string false PSU IP address
Psu-User-Agent header string false PSU browser User Agent
Psu-Referer header string false PSU Referer
Psu-Accept header string false PSU accept header
Psu-Accept-Charset header string false PSU charset
Psu-Accept-Encoding header string false PSU accept encoding
Psu-Accept-language header string false PSU accept language
Psu-Geo-Location header string false Comma separated latitude and longitude coordinates without spaces
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
DELETE https://api.enablebanking.com/sessions/{session_id} HTTP/1.1
Host: api.enablebanking.com
Accept: application/json
Psu-Ip-Address: string
Psu-User-Agent: string
Psu-Referer: string
Psu-Accept: string
Psu-Accept-Charset: string
Psu-Accept-Encoding: string
Psu-Accept-language: string
Psu-Geo-Location: -1.2345,6.789
Authorization: Bearer <JWT>
Responses
Status Description Schema
200 Successful Response SuccessResponse
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"message": "OK"
}
Accounts data
Get account details
GET /accounts/{account_id}/details
Fetching account details from ASPSP for an account by its ID
Parameters
Name In Type Required Description
account_id path string(uuid) true Account ID
Psu-Ip-Address header string false PSU IP address
Psu-User-Agent header string false PSU browser User Agent
Psu-Referer header string false PSU Referer
Psu-Accept header string false PSU accept header
Psu-Accept-Charset header string false PSU charset
Psu-Accept-Encoding header string false PSU accept encoding
Psu-Accept-language header string false PSU accept language
Psu-Geo-Location header string false Comma separated latitude and longitude coordinates without spaces
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
GET https://api.enablebanking.com/accounts/{account_id}/details HTTP/1.1
Host: api.enablebanking.com
Accept: application/json
Psu-Ip-Address: string
Psu-User-Agent: string
Psu-Referer: string
Psu-Accept: string
Psu-Accept-Charset: string
Psu-Accept-Encoding: string
Psu-Accept-language: string
Psu-Geo-Location: -1.2345,6.789
Authorization: Bearer <JWT>
Responses
Status Description Schema
200 Successful Response AccountResource
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"account_id": {
"iban": "FI0455231152453547"
},
"all_account_ids": [
{
"identification": "123456",
"scheme_name": "BBAN"
}
],
"account_servicer": {
"bic_fi": "string",
"clearing_system_member_id": {
"clearing_system_id": "NZNCC",
"member_id": 20368
},
"name": "string"
},
"name": "string",
"details": "string",
"usage": "PRIV",
"cash_account_type": "CACC",
"product": "string",
"currency": "string",
"psu_status": "string",
"credit_limit": {
"currency": "EUR",
"amount": "1.23"
},
"legal_age": true,
"postal_address": {
"address_type": "Business",
"department": "Department of resources",
"sub_department": "Sub Department of resources",
"street_name": "Vasavagen",
"building_number": "4",
"post_code": "00123",
"town_name": "Helsinki",
"country_sub_division": "Uusimaa",
"country": "FI",
"address_line": [
"Mr Asko Teirila PO Box 511",
"39140 AKDENMAA FINLAND"
]
},
"uid": "07cc67f4-45d6-494b-adac-09b5cbc7e2b5",
"identification_hash": "WwpbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoiaWJhbiIKXQpd.E8GzhnnsFC7K+4e3YMYYKpyM83Zx6toXrjgcvPP/Lqc=",
"identification_hashes": [
"WwpbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoiaWJhbiIKXQpd.E8GzhnnsFC7K+4e3YMYYKpyM83Zx6toXrjgcvPP/Lqc=",
"WwpbCiJhc3BzcF9uYW1lIgpdLApbCiJhc3BzcF9jb3VudHJ5IgpdLApbCiJhY2NvdW50IiwKImFjY291bnRfaWQiLAoib3RoZXIiLAoic2NoZW1lX25hbWUiCl0sClsKImFjY291bnQiLAoiYWNjb3VudF9pZCIsCiJvdGhlciIsCiJpZGVudGlmaWNhdGlvbiIKXQpd.AOm/TULGPD4a4GdcWhR9xh0GPlPUZuB2O1S9SYFWEz0="
]
}
Get account balances
GET /accounts/{account_id}/balances
Fetching account balances from ASPSP for an account by its ID
Parameters
Name In Type Required Description
account_id path string(uuid) true PSU account ID accessible in the provided session
Psu-Ip-Address header string false PSU IP address
Psu-User-Agent header string false PSU browser User Agent
Psu-Referer header string false PSU Referer
Psu-Accept header string false PSU accept header
Psu-Accept-Charset header string false PSU charset
Psu-Accept-Encoding header string false PSU accept encoding
Psu-Accept-language header string false PSU accept language
Psu-Geo-Location header string false Comma separated latitude and longitude coordinates without spaces
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
GET https://api.enablebanking.com/accounts/{account_id}/balances HTTP/1.1
Host: api.enablebanking.com
Accept: application/json
Psu-Ip-Address: string
Psu-User-Agent: string
Psu-Referer: string
Psu-Accept: string
Psu-Accept-Charset: string
Psu-Accept-Encoding: string
Psu-Accept-language: string
Psu-Geo-Location: -1.2345,6.789
Authorization: Bearer <JWT>
Responses
Status Description Schema
200 Successful Response HalBalances
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"balances": [
{
"name": "Booked balance",
"balance_amount": {
"currency": "EUR",
"amount": "1.23"
},
"balance_type": "CLAV",
"last_change_date_time": "2019-08-24T14:15:22Z",
"reference_date": "2019-08-24",
"last_committed_transaction": "4604aa90f8a8418092d80c3270846f0a"
}
]
}
Get account transactions
GET /accounts/{account_id}/transactions
Fetching account transactions from ASPSP for an account by its ID
Parameters
Name In Type Required Description
account_id path string(uuid) true PSU account ID accessible in the provided session
date_from query string(date) false Date to fetch transactions from (including the date, UTC timezone is assumed)
date_to query string(date) false Date to fetch transactions to (including the date, UTC timezone is assumed)
continuation_key query string false Key, allowing iterate over multiple API pages of transactions
transaction_status query TransactionStatus false Filter transactions by provided status
strategy query TransactionsFetchStrategy false Strategy how transaction are fetched
Psu-Ip-Address header string false PSU IP address
Psu-User-Agent header string false PSU browser User Agent
Psu-Referer header string false PSU Referer
Psu-Accept header string false PSU accept header
Psu-Accept-Charset header string false PSU charset
Psu-Accept-Encoding header string false PSU accept encoding
Psu-Accept-language header string false PSU accept language
Psu-Geo-Location header string false Comma separated latitude and longitude coordinates without spaces
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
GET https://api.enablebanking.com/accounts/{account_id}/transactions HTTP/1.1
Host: api.enablebanking.com
Accept: application/json
Psu-Ip-Address: string
Psu-User-Agent: string
Psu-Referer: string
Psu-Accept: string
Psu-Accept-Charset: string
Psu-Accept-Encoding: string
Psu-Accept-language: string
Psu-Geo-Location: -1.2345,6.789
Authorization: Bearer <JWT>
Responses
Status Description Schema
200 Successful Response HalTransactions
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"transactions": [
{
"entry_reference": "5561990681",
"merchant_category_code": "5511",
"transaction_amount": {
"currency": "EUR",
"amount": "1.23"
},
"creditor": {
"name": "MyPreferredAisp",
"postal_address": {
"address_line": [
"Mr Asko Teirila PO Box 511",
"39140 AKDENMAA FINLAND"
],
"address_type": "Business",
"building_number": "4",
"country": "FI",
"country_sub_division": "Uusimaa",
"department": "Department of resources",
"post_code": "00123",
"street_name": "Vasavagen",
"sub_department": "Sub Department of resources",
"town_name": "Helsinki"
}
},
"creditor_account": {
"iban": "FI0455231152453547"
},
"creditor_agent": {
"bic_fi": "string",
"clearing_system_member_id": {
"clearing_system_id": "NZNCC",
"member_id": 20368
},
"name": "string"
},
"debtor": {
"name": "MyPreferredAisp",
"postal_address": {
"address_line": [
"Mr Asko Teirila PO Box 511",
"39140 AKDENMAA FINLAND"
],
"address_type": "Business",
"building_number": "4",
"country": "FI",
"country_sub_division": "Uusimaa",
"department": "Department of resources",
"post_code": "00123",
"street_name": "Vasavagen",
"sub_department": "Sub Department of resources",
"town_name": "Helsinki"
}
},
"debtor_account": {
"iban": "FI0455231152453547"
},
"debtor_agent": {
"bic_fi": "string",
"clearing_system_member_id": {
"clearing_system_id": "NZNCC",
"member_id": 20368
},
"name": "string"
},
"bank_transaction_code": {
"description": "Utlandsbetalning",
"code": "12",
"sub_code": "32"
},
"credit_debit_indicator": "CRDT",
"status": "BOOK",
"booking_date": "2020-01-03",
"value_date": "2020-01-02",
"transaction_date": "2020-01-01",
"balance_after_transaction": {
"currency": "EUR",
"amount": "1.23"
},
"reference_number": "RF07850352502356628678117",
"reference_number_schema": "SEBG",
"remittance_information": [
"RF07850352502356628678117",
"Gift for Alex"
],
"debtor_account_additional_identification": {
"identification": "12345678",
"scheme_name": "CPAN"
},
"creditor_account_additional_identification": {
"identification": "12345678",
"scheme_name": "BBAN"
},
"exchange_rate": {
"unit_currency": "EUR",
"exchange_rate": "string",
"rate_type": "SPOT",
"contract_identification": "string",
"instructed_amount": {
"currency": "EUR",
"amount": "1.23"
}
},
"note": "string",
"transaction_id": "string"
}
],
"continuation_key": "string"
}
Get transaction details
GET /accounts/{account_id}/transactions/{transaction_id}
Fetching transaction details from ASPSP for an account transaction by its ID
Parameters
Name In Type Required Description
account_id path string(uuid) true Account ID
transaction_id path string true Transaction ID
Psu-Ip-Address header string false PSU IP address
Psu-User-Agent header string false PSU browser User Agent
Psu-Referer header string false PSU Referer
Psu-Accept header string false PSU accept header
Psu-Accept-Charset header string false PSU charset
Psu-Accept-Encoding header string false PSU accept encoding
Psu-Accept-language header string false PSU accept language
Psu-Geo-Location header string false Comma separated latitude and longitude coordinates without spaces
Authentication
To perform this operation, API requests must include Authorization header containing JWT calculated using private RSA key of the client application making the request. See jwtAuthentication.
Example request
GET https://api.enablebanking.com/accounts/{account_id}/transactions/{transaction_id} HTTP/1.1
Host: api.enablebanking.com
Accept: application/json
Psu-Ip-Address: string
Psu-User-Agent: string
Psu-Referer: string
Psu-Accept: string
Psu-Accept-Charset: string
Psu-Accept-Encoding: string
Psu-Accept-language: string
Psu-Geo-Location: -1.2345,6.789
Authorization: Bearer <JWT>
Responses
Status Description Schema
200 Successful Response Transaction
400 Bad Request ErrorResponse
401 Unauthorized ErrorResponse
403 Forbidden ErrorResponse
404 Not Found ErrorResponse
408 Request Timeout ErrorResponse
422 Unprocessable Entity ErrorResponse
429 Too Many Requests ErrorResponse
500 Internal Server Error ErrorResponse
Example responses
200 Response
{
"entry_reference": "5561990681",
"merchant_category_code": "5511",
"transaction_amount": {
"currency": "EUR",
"amount": "1.23"
},
"creditor": {
"name": "MyPreferredAisp",
"postal_address": {
"address_line": [
"Mr Asko Teirila PO Box 511",
"39140 AKDENMAA FINLAND"
],
"address_type": "Business",
"building_number": "4",
"country": "FI",
"country_sub_division": "Uusimaa",
"department": "Department of resources",
"post_code": "00123",
"street_name": "Vasavagen",
"sub_department": "Sub Department of resources",
"town_name": "Helsinki"
}
},
"creditor_account": {
"iban": "FI0455231152453547"
},
"creditor_agent": {
"bic_fi": "string",
"clearing_system_member_id": {
"clearing_system_id": "NZNCC",
"member_id": 20368
},
"name": "string"
},
"debtor": {
"name": "MyPreferredAisp",
"postal_address": {
"address_line": [
"Mr Asko Teirila PO Box 511",
"39140 AKDENMAA FINLAND"
],
"address_type": "Business",
"building_number": "4",
"country": "FI",
"country_sub_division": "Uusimaa",
"department": "Department of resources",
"post_code": "00123",
"street_name": "Vasavagen",
"sub_department": "Sub Department of resources",
"town_name": "Helsinki"
}
},
"debtor_account": {
"iban": "FI0455231152453547"
},
"debtor_agent": {
"bic_fi": "string",
"clearing_system_member_id": {
"clearing_system_id": "NZNCC",
"member_id": 20368
},
"name": "string"
},
"bank_transaction_code": {
"description": "Utlandsbetalning",
"code": "12",
"sub_code": "32"
},
"credit_debit_indicator": "CRDT",
"status": "BOOK",
"booking_date": "2020-01-03",
"value_date": "2020-01-02",
"transaction_date": "2020-01-01",
"balance_after_transaction": {
"currency": "EUR",
"amount": "1.23"
},
"reference_number": "RF07850352502356628678117",
"reference_number_schema": "SEBG",
"remittance_information": [
"RF07850352502356628678117",
"Gift for Alex"
],
"debtor_account_additional_identification": {
"identification": "12345678",
"scheme_name": "CPAN"
},
"creditor_account_additional_identification": {
"identification": "12345678",
"scheme_name": "BBAN"
},
"exchange_rate": {
"unit_currency": "EUR",
"exchange_rate": "string",
"rate_type": "SPOT",
"contract_identification": "string",
"instructed_amount": {
"currency": "EUR",
"amount": "1.23"
}
},
"note": "string",
"transaction_id": "string"
}
@@ -0,0 +1,224 @@
# Business Banking Sandbox Setup Guide
## Overview
This guide explains how to configure Enable Banking to use business banking test data instead of personal banking data.
## Changes Made
### 1. Transaction Fetching Added
- ✅ Callback route now fetches transactions for each account
- ✅ Transactions are stored in `landing.transactions` table
- ✅ Error handling per account (continues if one account fails)
### 2. PSU Type Configuration
- ✅ Added `ENABLE_BANKING_PSU_TYPE` environment variable
- ✅ Code now uses this variable throughout the flow
- ✅ Defaults to `business` if not set
### 3. Sample Business Data Created
- ✅ Swedish business banking sample data in `SAMPLE_BUSINESS_BANKING_DATA.json`
- ✅ Realistic business transactions (invoices, rent, VAT, payroll, etc.)
- ✅ Swedish IBANs and business-specific patterns
## Environment Configuration
Your `.env.local` now includes:
```bash
ENABLE_BANKING_PSU_TYPE=business # or 'personal'
```
### Switching Between Personal and Business
**For Business Banking:**
```bash
ENABLE_BANKING_PSU_TYPE=business
```
**For Personal Banking:**
```bash
ENABLE_BANKING_PSU_TYPE=personal
```
## Sample Business Banking Data
The file `dev_docs/SAMPLE_BUSINESS_BANKING_DATA.json` contains:
### Account Details
- **Account Name**: Arcim AB Företagskonto
- **IBAN**: SE4550000000058398257466
- **Currency**: SEK
- **Balance**: 847,250.50 SEK
- **Type**: Business checking account (CACC)
### Sample Transactions (10 total)
1. **Customer Payment** - 125,000 SEK (credit)
- Faktura 2025-1045 Konsulttjänster Oktober
2. **Rent Payment** - 45,000 SEK (debit)
- Hyra Lokaler Q4 2025
3. **Supplier Payment** - 28,500 SEK (debit)
- Faktura KP-2025-0892 Konsulttjänster
4. **Customer Payment** - 89,000 SEK (credit)
- Betalning Faktura 2025-3421 Projektleverans
5. **VAT Payment** - 156,700 SEK (debit)
- Moms Q3 2025 - Organisationsnummer 556789-1234
6. **AWS Cloud** - 12,500 SEK (debit)
- AWS Cloud Services October 2025
7. **Payroll** - 245,000 SEK (debit)
- Löner Oktober 2025 - 5 anställda
8. **Customer Payment** - 175,000 SEK (credit)
- Faktura 2025-2189 Utveckling SaaS Plattform
9. **Telecom** - 8,500 SEK (debit)
- Företagsabonnemang Telefoni & Internet Oktober
10. **Insurance** - 32,000 SEK (debit)
- Företagsförsäkring Q4 2025
## Testing the Integration
### Current Status
✅ **Working:**
- Authentication with Enable Banking
- Bank connection storage
- Account storage
- Transaction fetching and storage
### Test Flow
1. **Clear existing data** (if needed):
```sql
DELETE FROM landing.transactions WHERE tenant_id = 'your_tenant_id';
DELETE FROM landing.bank_accounts WHERE tenant_id = 'your_tenant_id';
DELETE FROM landing.bank_connections WHERE tenant_id = 'your_tenant_id';
```
2. **Start dev server**:
```bash
npm run dev
```
3. **Connect to bank**:
- Go to `/banking/connect`
- Select a bank
- Click "Create Account" on mock ASPSP page
- Set up sample data (use the business sample structure)
- Grant consent
4. **Verify data in Snowflake**:
```sql
-- Check connections
SELECT * FROM landing.bank_connections
WHERE tenant_id = 'your_tenant_id'
ORDER BY created_at DESC;
-- Check accounts
SELECT * FROM landing.bank_accounts
WHERE tenant_id = 'your_tenant_id'
ORDER BY created_at DESC;
-- Check transactions
SELECT * FROM landing.transactions
WHERE tenant_id = 'your_tenant_id'
ORDER BY booking_date DESC;
```
5. **Check logs**:
Look for these in your terminal:
```
Fetching transactions for account acc-business-001
Found 10 transactions for account acc-business-001
```
## Troubleshooting
### No Transactions Stored
**Symptoms:** Accounts are created but no transactions
**Possible Causes:**
1. The mock ASPSP didn't create transaction data
2. The account UID is wrong
3. Transaction API returned error
**Debug:**
- Check terminal logs for "Fetching transactions" messages
- Check for error messages like "Failed to fetch transactions"
- Verify the account `uid` field is correct
### Wrong Data Type (Personal vs Business)
**Symptoms:** Getting personal accounts when expecting business
**Solution:**
1. Check `.env.local` has `ENABLE_BANKING_PSU_TYPE=business`
2. Restart dev server (environment variables only load on startup)
3. Create a new test account on mock ASPSP page
4. Try selecting a different bank from the list
### Transaction Format Issues
**Symptoms:** Transactions stored but with missing/wrong data
**Check:**
- Transaction amounts are parsed correctly (converts string to number)
- Remittance info is joined properly (array to string)
- Credit/debit indicator is mapped correctly
## Using the Sample Data Structure
The `SAMPLE_BUSINESS_BANKING_DATA.json` file shows the expected structure for business banking data. While you can't directly upload this to Enable Banking's mock ASPSP, you can:
1. **Use it as reference** when creating test data on the mock ASPSP page
2. **Manually insert** test data into your Snowflake tables for testing:
```sql
-- Example: Insert test transactions directly
INSERT INTO landing.transactions (
transaction_id, account_id, tenant_id, external_transaction_id,
booking_date, value_date, amount, currency, description,
counterparty_name, counterparty_account, transaction_type
) VALUES
('txn_test_001', 'your_account_id', 'your_tenant_id', '2025102101',
'2025-10-18', '2025-10-18', 125000.00, 'SEK',
'Faktura 2025-1045 Konsulttjänster Oktober',
'KUND AB', 'SE9950000000054740013810', 'credit');
```
3. **Request Enable Banking** to pre-populate business test data for your sandbox application
## Next Steps
### For Production
When moving to production:
1. Keep `ENABLE_BANKING_PSU_TYPE=business`
2. Register production application at Enable Banking
3. Update environment variables with production credentials
4. Real bank data will automatically populate
### For Enhanced Testing
Consider:
- Creating multiple test companies with different transaction patterns
- Testing edge cases (failed transactions, pending transactions)
- Testing different currencies (if expanding beyond Sweden)
- Testing large transaction volumes
## Support
If you encounter issues:
- Check Enable Banking documentation: https://enablebanking.com/docs
- Contact Enable Banking support: info@enablebanking.com
- Review logs in your terminal and Snowflake
@@ -0,0 +1,148 @@
UI Widgets
Terms consent
This widget provides the possibility to display to an end user terms of the service and acquire their consent before redirecting them to tilisy.enablebanking.com for authorisation of the requested access in an ASPSP.
NB
The widget shall be used only when your application relies on Enable Banking accessing ASPSPs as a regulated entity. If your company is a licensed TPP and Enable Banking acts solely as a technical service provider, the terms of service step is always skipped during the authorisation flow.
In order to use the widget, the following is needed.
Load the widgets library https://tilisy.enablebanking.com/lib/widgets.umd.min.js on the page where it is going to be used.
<script src="https://tilisy.enablebanking.com/lib/widgets.umd.min.js"></script>
Put the custom element enablebanking-consent registered by the widgets library into the place on the page where the widget needs to be shown.
<enablebanking-consent
id="enablebanking-consent"
authorization="a8bfe9f4-dfdf-4c86-9a94-9db7660bd4bd"
locale="SV"
can-cancel
sandbox></enablebanking-consent>
The element enablebanking-consent has the following attributes:
authorization (required), should contain authorisation ID received from POST /auth API call;
locale (optional), language in which the widget content should be presented. Supported languages: DA, EN, ET, FI, FR, LT, LV, NL, NO, PL, RU, SV;
can-cancel (optional), when present the “Cancel” button will be displayed, which will emit cancel event when pressed;
sandbox (optional), to be used when authorisation was initiated with an application registered to sandbox environment;
origin (optional), to be provided in case a custom/dedicated environment is used, the default value is https://tilisy.enablebanking.com;
no-redirect (optional), to be used if the end user should not be automatically redirected to tilisy.enablebanking.com for authorisation of the access in an ASPSP; in this case redirect is to be performed when confirmed event is triggered.
Using event listener function
<script>
document.getElementById("enablebanking-consent").addEventListener("confirmed", function(e) {
console.log(e)
});
</script>
The element produces the following events:
error, if an error occurs,
ready, when the widget is fully loaded,
confirmed, after a user has confirmed the consent,
cancelled, if the “Cancel” button was pressed.
The events can be listened similarly to standard Javascript events using addEventListener method called for the enablebanking-consent element.
The widget does not include any CSS, it will use the styles present on the page where included.
NB
The widget can be used only on the websites with origins whitelisted for the application used to initiate end user authorisation.
Whitelisting of the origins can be done through the control panel:
Go to https://enablebanking.com/cp/applications (opens new window);
Choose Edit from the context menu for your application ("⋮" next to the application name);
Enter necessary origins into the "Allowed widget origins" edit box;
Press the "Save" button.
ASPSP selection
This widget provides a method to present on a web page the list of available ASPSPs (i.e. banks and similar financial institutions) and let an end-user to select the one, which they want to proceed with.
The following code is needed in the html file.
Load the widgets library https://tilisy.enablebanking.com/lib/widgets.umd.min.js and the default CSS https://tilisy.enablebanking.com/lib/widgets.css on the page where it is going to be used.
<script src="https://tilisy.enablebanking.com/lib/widgets.umd.min.js"></script>
<link href="https://tilisy.enablebanking.com/lib/widgets.css" rel="stylesheet">
Put the custom element enablebanking-aspsp-list registered by the widgets library into the place on the page where the widget needs to be shown.
<enablebanking-aspsp-list
id="enablebanking-aspsp-list"
country="FI"
psu-type="personal"
service="AIS"
sandbox></enablebanking-aspsp-list>
Add an event listener, which would trigger authorisation of access to account information or payment initiation.
<script>
document.getElementById("enablebanking-aspsp-list").addEventListener("selected", function(e) {
console.log(e.detail)
});
</script>
When an end-user clicks one of the ASPSP cards, the event selected will be triggered and the detail field value is like this:
{
"beta": false,
"country": "SE",
"name": "Ekeby Sparbank",
"psuType": "personal",
"sandbox": true,
"service": "AIS"
}
The element enablebanking-aspsp-list has the following attributes:
country (required), two-letter country code determining which ASPSPs will be displayed;
psu-type (required), either personal or business determining the type of user, which will grant authorisation;
service (required), either AIS or PIS determining whether account information or payment initiation service will be used;
sandbox (optional), to be provided in case sandbox authorisation will take place in the sandbox environment;
no-beta (optional), can be used to filter out ASPSPs whose integrations are still in the beta-testing phase;
origin (optional), to be provided in case a custom/dedicated environment is used, the default value is https://tilisy.enablebanking.com;
search-term (optional), can be used to filter ASPSPs by name;
logo-transform (optional), can be used change dimentions of the logo image (for example, -/resize/320x/-/crop/440x340/center/, for full list of possible transformations, please refer to https://uploadcare.com/docs/transformations/image/resize-crop/).
The element produces the following events:
error, if an error occurs;
ready, when the widget is fully loaded or them the list of ASPSP is updated;
selected, after a user has selected an ASPSP.
Auth flow
This widget provides the possibility to perform interactions with an end user necessary for authorisation of access to account information and payment initiation from a web page hosted by the application accessing Enable Banking API and, if necessary, redirect directly ASPSPs bypassing redirect to tilisy.enablebanking.com (or a custom domain, in case the dedicated single-tenant environment is used).
In order to use the widget, the following is needed.
Load the widgets library https://tilisy.enablebanking.com/lib/widgets.umd.min.js on the page where it is going to be used.
<script src="https://tilisy.enablebanking.com/lib/widgets.umd.min.js"></script>
Put the custom element enablebanking-auth-flow registered by the widgets library into the place on the page where the widget needs to be shown.
<enablebanking-auth-flow
id="enablebanking-auth-flow"
authorization="a8bfe9f4-dfdf-4c86-9a94-9db7660bd4bd"
locale="SV"
can-cancel
sandbox></enablebanking-auth-flow>
The element enablebanking-auth-flow has the following attributes:
authorization (conditional), should contain authorisation ID received from POST /auth API call;
payment (conditional), should contain payment ID received from POST /payment API call;
locale (optional), language in which the widget content should be presented. Supported languages: DA, EN, ET, FI, FR, LT, LV, NL, NO, PL, RU, SV;
sandbox (optional), to be used when authorisation was initiated with an application registered to sandbox environment;
origin (optional), to be provided in case a custom/dedicated environment is used, the default value is https://tilisy.enablebanking.com.
Using event listener function
<script>
document.getElementById("enablebanking-auth-flow").addEventListener("ready", function(e) {
console.log("Auth flow widget is loaded")
});
</script>
The element produces the following events:
error, if an error occurs,
ready, when the widget is fully loaded,
ais-loaded, after authorisation session for AIS service is established,
pis-loaded, after authorisation session for PIS service is established.
The events can be listened similarly to standard Javascript events using addEventListener method called for the enablebanking-auth-flow element.
The widget includes default CSS, which can be overriden.
NB
The widget can be used only on the websites with origins whitelisted for the application used to initiate end user authorisation.
Whitelisting of the origins can be done through the control panel:
Go to https://enablebanking.com/cp/applications (opens new window);
Choose Edit from the context menu for your application ("⋮" next to the application name);
Enter necessary origins into the "Allowed widget origins" edit box;
Press the "Save" button.
@@ -0,0 +1,198 @@
Quick Start with Enable Banking API
Welcome to the Quick Start guide for the Enable Banking API! This guide will help you quickly get up and running with the API so you can start building innovative financial applications.
Signing up for an account
Before you can begin using the Enable Banking API, you'll need to sign up for an account to Enable Banking Control Panel. Follow these steps:
Visit the authentication page https://enablebanking.com/sign-in/ (opens new window).
Enter your email, new accounts are automatically created on the first sign in.
Follow the one-time authentication link sent to your email. After authentication you will be redirected your profile in the Control.
Registering an application
Once you have an account, you can register your application to obtain API access:
Go to the API applications (opens new window)page using the top menu of the Control Panel.
Fill out "Add a new application" form:
Keep the Sandbox environment and the default option for creation of the application's private key;
Fill in the name of your application (this name will be shown to end users when they will be requested to authorise sharing of their account information with your application or to confirm a payment initiated by your application);
Enter URLs whitelisted for redirecting of end users after they complete authorisation of access to account information or confirm a payment.
Submit the form by pressing "Register" button. Your web browser will generate a private key for the application and it will be saved into your downloads folder. The file name will be the ID that was assigned to the newly registered application (e.g., aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.pem).
Creating JWT for API authorisation
To authenticate with the API, your application will need to use JSON Web Tokens generated using the private key saved during the registration process described above. Here is a sample implementation:
Import a library allowing to generate JSON Web Tokens using RS256 algorithm:
PythonJavaScript
import jwt as pyjwt
Most modern languages will have a number of libraries for JWT generation in their ecosystems.
An extensive list of libraries for JWT generation can be found at https://jwt.io/libraries (opens new window).
Import other necessary libraries and write the necessary helper functions:
PythonJavaScript
import os
from datetime import datetime
This part significantly varies depending on the programming language you choose and the library you use for JWT generation.
Read application's private key from a file:
PythonJavaScript
private_key = open("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.pem", "rb").read()
Note that some libraries do not require you to load the private key from a file but rather allow you to pass the path to the file as a parameter.
Prepare the JWT payload (also known as the body):
PythonJavaScript
iat = int(datetime.now().timestamp())
jwt_body = {
"iss": "enablebanking.com", # always the same value
"aud": "api.enablebanking.com", # always the same value
"iat": iat, # time when the JSON Web Token is created
"exp": iat + 3600, # time when the token is set to expire
}
Create the JWT with its header and signature:
PythonJavaScript
jwt = pyjwt.encode(
jwt_body,
private_key,
algorithm="RS256",
headers={
"kid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", # your application's ID
}
)
Prepare the authorisation header for sending with every API request:
PythonJavaScript
base_headers = {
"Authorization": f"Bearer {jwt}",
}
Your application needs to send the above created authorisation header with every request it makes to Enable Banking API.
The full specification of the JWT format expected by Enable Banking API can be found in the API reference.
Accessing account information
With authentication in place, you can start accessing account information from ASPSPs (banks and similar financial institutions):
To obtain the list of available ASPSPs in a country, send a GET request to the ASPSPs endpoint specifying the desired country in the country query parameter in the format of Two-letter ISO 3166 code:
PythonJavaScript
import requests
from pprint import pprint
r = requests.get("https://api.enablebanking.com/aspsps?country=FI", headers=base_headers)
# If you want you can override BANK_NAME and BANK_COUNTRY with any bank from this list
print("Available ASPSPs:")
pprint(r.json()["aspsps"])
The first step in obtaining account information is to start the authorisation process. To do this, send a POST request to the authorisation endpoint, specifying a bank name and a country from the list returned from ASPSPs endpoint.
PythonJavaScript
body = {
"access": {
"valid_until": (datetime.now(timezone.utc) + timedelta(days=10)).isoformat() # 10 days ahead
},
"aspsp": {
"name": "Nordea", # BANK_NAME
"country": "FI" # BANK_COUNTRY
},
"state": "123e4567-e89b-12d3-a456-426614174000"
"redirect_url": "https://example.com/redirect", # application's redirect URL
"psu_type": "personal",
}
r = requests.post("https://api.enablebanking.com/auth", json=body, headers=base_headers)
auth_url = r.json()["url"]
The response will contain a redirect URL to which you should redirect the end user to complete the authorisation process. After the end user completes the authorisation process, they will be redirected to the URL you specified during the application registration. The URL will contain a query parameter named code which will be used to authorize the user session
PythonJavaScript
print(f"To authenticate open URL {auth_url}") # open this URL in a web browser
To authorize the user session send a POST request to the sessions endpoint, specifying the code received in the authorisation endpoint. In the response you will receive a session ID, with the list of authorized accounts.
PythonJavaScript
r = requests.post(f"https://api.enablebanking.com/sessions", json={"code": code}, headers=base_headers)
session = r.json()
print("New user session has been created:")
pprint(session)
To obtain the list of balances for the authorized accounts, send a GET request to the balances endpoint, specifying the account ID in the URL.
PythonJavaScript
# Using the first available account for the following API calls
account_uid = session["accounts"][0]["uid"]
# Retrieving account balances
r = requests.get(f"https://api.enablebanking.com/accounts/{account_uid}/balances", headers=base_headers)
print("Balances:")
pprint(r.json())
To obtain the list of transactions for the authorized accounts, send a GET request to the transactions endpoint, specifying the account ID in the URL.
PythonJavaScript
r = requests.get(f"https://api.enablebanking.com/accounts/{account_uid}/transactions",headers=base_headers)
resp_data = r.json()
print("Transactions:")
pprint(resp_data["transactions"])
you can refer to the API reference for more details on the API endpoints.
Full source code in our GitHub:
PythonJavaScript
https://github.com/enablebanking/enablebanking-api-samples/blob/master/python_example/account_information.py(opens new window)
Initiating payments
If your application requires payment initiation functionality, you can use the Enable Banking API for this purpose:
To initiate a payment, send a POST request to the Create Payment endpoint, specifying the payment details in the request body.
PythonJavaScript
body = {
"payment_type": "SEPA",
"payment_request": {
"credit_transfer_transaction": [
{
"beneficiary": {
"creditor_account": {
"scheme_name": "IBAN",
"identification": "FI7473834510057469",
},
"creditor": {
"name": "Test",
},
},
"instructed_amount": {"amount": "2.00", "currency": "EUR"},
"reference_number": "123",
}
],
},
"aspsp": {"name": "Nordea", "country": "FI"},
"state": "123e4567-e89b-12d3-a456-426614174000",
"redirect_url": "https://example.com/redirect", # application's redirect URL
"psu_type": "personal",
}
r = requests.post(f"https://api.enablebanking.com/payments", json=body, headers=base_headers)
payment = r.json()
The response will contain a redirect URL to which you should redirect the end user to complete the payment initiation process. Use following credentials to authenticate: customera / 12345678
PythonJavaScript
print("To authenticate open URL:")
print(payment["url"])
To get the status of the initiated payment, send a GET request to the Get Payment endpoint, specifying the payment ID in the URL.
PythonJavaScript
# This request can be called multiple times to check the status of the payment
payment_id = payment["payment_id"]
r = requests.get(f"https://api.enablebanking.com/payments/{payment_id}", headers=base_headers)
print("Payment status:")
pprint(r.json())
Full source code in our GitHub:
PythonJavaScript
https://github.com/enablebanking/enablebanking-api-samples/blob/master/python_example/payment_initiation.py(opens new window)
Next steps
Congratulations! You've completed the essential steps to get started with the Enable Banking API. Here are some suggested next steps:
Explore the API reference to learn more about the API endpoints and their parameters.
Check out code samples and a Postman collections in our GitHub repository (opens new window).
Discover the Control Panel where you can manage apps, configure settings, and monitor activity.
Learn how to test your API integrations in the Sandbox.
If you have any questions or run into issues, don't hesitate to reach out for assistance.
Happy coding!
@@ -0,0 +1,210 @@
Kickstart Open Banking and Enrichment in less than two hours
Get started quickly with connecting Open Banking, accessing raw account data, and creating financial insights.
Explore our docs
1
Enable Banking
Get started with accessing raw account data.
This documentation might not be fully up to date. You can view the latest information on Enable Bankings documentation page: https://enablebanking.com/docs/api/quick-start
1. Signing up for an account
Before you can begin using the Enable Banking API, you'll need to sign up for an account to Enable Banking Control Panel. Follow these steps.
1.1 Visit the authentication page here
1.2 Enter your email, new accounts are automatically created on the first sign in
1.3 Follow the one-time authentication link sent to your email. After authentication you will be redirected your profile in the Control
2. Registering an application
Once you have an account, you can register your application to obtain API access.
2.1 Go to the API applications page using the top menu of the Control Panel here
2.2 Fill out "Add a new application" form
2.2.1 Keep the Sandbox environment and the default option for creation of the application's private key
2.2.2 Fill in the name of your application (this name will be shown to end users when they will be requested to authorise sharing of their account information with your application or to confirm a payment initiated by your application)
2.2.3 Enter URLs whitelisted for redirecting of end users after they complete authorisation of access to account information or confirm a payment
2.3 Submit the form by pressing "Register" button. Your web browser will generate a private key for the application and it will be saved into your downloads folder. The file name will be the ID that was assigned to the newly registered application (e.g., aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.pem)
3. Creating JWT for API authorisation
To authenticate with the API, your application will need to use JSON Web Tokens generated using the private key saved during the registration process described above. The full specification of the JWT format expected by Enable Banking API can be found in the API reference. Here is a sample implementation
3.1 Import a library allowing to generate JSON Web Tokens using RS256 algorithm
const jwa = require("jwa")
Most modern languages will have a number of libraries for JWT generation in their ecosystems. An extensive list of libraries for JWT generation can be found at https://jwt.io/libraries (opens new window).
3.2 Import other necessary libraries and write the necessary helper functions
const fs = require("fs")
const jsonBase64 = (data) => {
return Buffer.from(JSON.stringify(data)).toString("base64").replace("=", "")
}
This part significantly varies depending on the programming language you choose and the library you use for JWT generation.
3.3 Read application's private key from a file
const privateKey = fs.readFileSync("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.pem", "utf8")
Note that some libraries do not require you to load the private key from a file but rather allow you to pass the path to the file as a parameter.
3.4 Prepare the JWT payload (also known as the body)
const iat = Math.floor((new Date()).getTime() / 1000)
const jwtBody = {
iss: "enablebanking.com", // always the same value
aud: "api.enablebanking.com", // always the same value
iat: iat, // time when the token is created
exp: iat + 3600 // time when the token is set to expire
}
3.5 Create the JWT with its header and signature
const jwt = ((exp = 3600) => {
const header = jsonBase64({
typ: "JWT",
alg: "RS256",
kid: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" // your application's ID
})
const body = jsonBase64(jwtBody)
const signature = jwa("RS256").sign((header + '.' + body), privateKey)
return (header + '.' + body + '.' + signature)
})()
3.6 Prepare the authorisation header for sending with every API request
const baseHeaders = {
Authorization: "Bearer " + jwt
}
Your application needs to send the above created authorisation header with every request it makes to Enable Banking API.
4. Accessing account information
With authentication in place, you can start accessing account information from ASPSPs (banks and similar financial institutions).
4.1 To obtain the list of available ASPSPs in a country, send a GET request to the ASPSPs endpoint specifying the desired country in the country query parameter in the format of Two-letter ISO 3166 code
const fetch = require('node-fetch');
const aspspsResponse = await fetch('https://api.enablebanking.com/aspsps?country=FI', {
headers: baseHeaders
})
// If you want you can override BANK_NAME and BANK_COUNTRY with any bank from this list
console.log('Available ASPSPs: ' + await aspspsResponse.text())
4.2 The first step in obtaining account information is to start the authorisation process. To do this, send a POST request to the authorisation endpoint, specifying a bank name and a country from the list returned from ASPSPs endpoint
// 10 days ahead
const validUntil = new Date(new Date().getTime() + 10 * 24 * 60 * 60 * 1000);
const startAuthorizationBody = {
access: {
valid_until: validUntil.toISOString()
},
aspsp: {
name: "Nordea", // BANK_NAME
country: "FI" // BANK_COUNTRY
},
state: "123e4567-e89b-12d3-a456-426614174000",
redirect_url: "https://example.com/redirect", // application's redirect URL
psu_type: "personal"
}
const startAuthorizationResponse = await fetch('https://api.enablebanking.com/auth', {
method: "POST",
headers: baseHeaders,
body: JSON.stringify(startAuthorizationBody)
})
const startAuthorizationData = await startAuthorizationResponse.text();
console.log('Start authorization data: ' + startAuthorizationData)
4.3 The response will contain a redirect URL to which you should redirect the end user to complete the authorisation process. After the end user completes the authorisation process, they will be redirected to the URL you specified during the application registration. The URL will contain a query parameter named code which will be used to authorize the user session
console.log('To authenticate open URL ' + startAuthorizationData)
4.4 To authorize the user session send a POST request to the sessions endpoint, specifying the code received in the authorisation endpoint. In the response you will receive a session ID, with the list of authorized accounts
const createSessionBody = {
code: code
}
const createSessionResponse = await fetch('https://api.enablebanking.com/sessions', {
method: "POST",
headers: baseHeaders,
body: JSON.stringify(createSessionBody)
})
const session = await createSessionResponse.text()
console.log('New user session has been created: ' + session)
4.5 To obtain the list of balances for the authorized accounts, send a GET request to the balances endpoint, specifying the account ID in the URL
// Using the first available account for the following API calls
const accountId = JSON.parse(session).accounts[0]
const accountBalancesResponse = await fetch('https://api.enablebanking.com/accounts/' + accountId + '/balances', {
headers: baseHeaders
})
console.log('Account balances data: ' + accountBalancesResponse.text())
4.6 To obtain the list of balances for the authorized accounts, send a GET request to the balances endpoint, specifying the account ID in the URL
const accountTransactionsResponse = await fetch('https://api.enablebanking.com/accounts/' + accountId + '/transactions', {
headers: baseHeaders
})
console.log('Account transactions data: ' + accountTransactionsResponse.text()')
2
Gokind
Create insights from the raw account data
1. Fetch an API key pair
Get your first API key pair.
1.1 Visit the portal here
1.2 Request a key pair and await verification
1.3 Once your key pair has been verified download it and store it safely
2. Get an access token
JWT valid for two hours.
const axios = require('axios');
const { token } = await axios.post('https://api.gokind.co/auth', {
publicKey: "{YOUR_PUBLIC_KEY}",
privateKey: "{YOUR_PRIVATE_KEY}"
})M
console.log('JWT token valid for two hours: ' + token);
3. Format the Enable Banking data
Make it compatible with the Gokind API.
const formattedBundles = transactionFromEnableBanking.map(transaction => {
const {
remittance_information,
creditor,
debtor,
bank_transaction_code,
transaction_amount,
value_date,
booking_date,
transaction_date,
} = transaction;
const {description, sub_code, code} = bank_transaction_code;
const {amount, currency} = transaction_amount;
return {
label: remittance_information.join(' '),
creditor: creditor.name,
debtor: debtor.name,
type: description || code,
subType: sub_code,
amount: amount,
currency: currency,
date: value_date || booking_date || transaction_date
}
});
console.log('Formatted bundles: ' + JSON.stringify(formattedBundles));
4. Get the enriched response
Enrich the formatted bundles.
const axios = require('axios');
const headers = {
'Authorization': 'Bearer ' + token
};
const body = {
identifiers:{
account:{
type: "business",
},
bundles: formattedBundles
},
include:{
logo: true,
industries: true,
payment: true,
checks: true,
flags: true,
event: true,
},
config:{
region: "SE",
return: {
unidentified: true
}
}
};
const response = await axios.post('https://api.gokind.co/identify', body, { headers });
console.log(response);
+357
View File
@@ -0,0 +1,357 @@
# Förtydligad feedback med tolkningar
## 1. Onboarding-flöde
**Influencer Business → Influencer Assistant**
- "Influencer Business" står troligen som appnamn/rubrik i onboarding-flödet
- Ska ersättas med "Influencer Assistant" överallt där det förekommer
- Förmodligen i header, sidtitel och eventuellt metadata
**Localhost redirect problem vid auth genom mail**
- Magic link-autentisering redirectar till `localhost:3000` istället för produktions-URL
- Environment variable för NEXT_PUBLIC_SITE_URL är förmodligen inte satt korrekt i Supabase
**Ta bort alla detaljer kring EF/AB**
- Nuvarande onboarding har förklarande text typ "Enklare bokföring, lägre egenavgifter" vid bolagsformsval
- Ska bli en ren dropdown med endast "Enskild firma", "Aktiebolag", "Inget bolag ännu (frilans)"
- Ingen pedagogisk text, bara valet
**Lägg till "Inget bolag ännu (frilans)"**
- Tredje alternativ för creators som tar sina första samarbeten men inte formaliserat verksamheten än
- Låter dem använda appen för att spåra inkomster innan de registrerar bolag
- Förmodligen med begränsad funktionalitet (ingen bokföring, bara spårning)
**Verksamhetsnamn lägg till (Eller ditt namn vid EF)**
- Labeln "Verksamhetsnamn" ska ha suffix-text "(Eller ditt namn vid EF)"
- Placeholder bör vara "Alices Influencer-verksamhet" eller "Alice Andersson"
- Gör det tydligt att EF-användare kan använda personnamn
**Org nummer förklara att det är ens personnummer vid EF**
- Placeholder-text: "ÅÅMMDD-XXXX (ditt personnummer vid EF)"
- Eller hjälptext under fältet: "Vid enskild firma är orgnummer samma som ditt personnummer"
- Görs obligatoriskt för EF/AB (ta bort "frivilligt")
**Ta bort frivilligt för EF**
- Orgnummer är obligatoriskt för både EF och AB
- Required-validering ska gälla
**Ändra till brun färg där det är grönt**
- Primärfärg i appen är troligen grön idag
- Ska bytas till brun enligt brand guidelines
- Gäller knappar, accenter, aktiva tillstånd
**Bankkonto ta bort? Eller direkt till Enable Banking?**
- Nuvarande flöde: manuellt IBAN-fält i onboarding
- Ska ersättas med Enable Banking-integration direkt
- Dvs. istället för textfält → knapp "Koppla bankkonto" som öppnar Enable Banking
- Hoppar över manuell input helt? Eller?
**Fixa knapp så att den går direkt vid klick på bank och inte två steg**
- Nuvarande: Klicka på en bank , sedan ändå behöva scrolla ner och klicka "autentisera till X" knapp.
---
## 2. Dashboard (första sidan)
**"Godmorgon Alice, idag ska du göra X" eller "Allt är som det ska"**
- Personaliserad hero-sektion högst upp på dashboarden
- "Godmorgon Alice, idag ska du: Skicka faktura till H&M (förfaller om 3 dagar), Betala moms (deadline 12 feb)"
- Om inga pending items: "Godmorgon Alice, allt är som det ska"
- Hämtar från kommande kalenderhändelser + fakturor nära förfall + obetalda fakturor
**Pengarna under**
- "Pengarna under" = summan pengar som finns under/kvar efter att preliminärskatt dragits av
- Visar nettoresultat efter beräknad skatt
- Dashboard ska visa: Bruttoinkomst, Beräknad skatt, Kvar att ta ut
- "Kvar att ta ut: 45 000 kr" typ
**Undersök skatteberäkning?**
**Intäkter / Kostnader också 0, undersök**
- Dashboard visar 0 kr för både intäkter och kostnader, undersök detta om det stämmer
**Ny kampanj → Nytt samarbete**
- CTA-knapp eller sektionshuvud säger "Ny kampanj"
- Ska vara "Nytt samarbete" eller "+ Skapa samarbete"
- Matchar creators mentala modell bättre
**Spåra leverabler svårt ord**
- "Leverabler" är projektledarjargong, inte creator-språk
- Ska vara "Spåra innehåll" eller "Håll koll på publiceringar"
- Mindre formellt, mer intuitivt
**Ta bort logga körning från första sidan**
- Det finns en sektion/knapp för mileage tracking på dashboarden
- Ska tas bort helt (inte relevant för målgruppen)
- Creators kör sällan tjänsteresor, prioritera inte detta
**Skita i personlig kortkoppling, arbeta endast med företagskort**
- Endast företagskort via Enable Banking
- Kategorisera alla transaktioner från företagskonto
- Enklare modell, färre edge cases
**Ta bort sammanfattning månadsvis kalendervy**
- Kalendervyn har förmodligen en rad per månad som aggregerar events
- Tar onödigt utrymme och är inte så användbar
- Visa bara enskilda events i kronologisk ordning
**Kalender större och mer tilltalande**
- Nuvarande kalender är förmodligen en liten widget eller sidomodul
- Ska ta mer plats (förmodligen halva skärmbredden eller större)
- Kanske full-width på mobil
- Mer visuellt prominent, då det är core feature
**Ta bort "Håll koll på betalningar och deadlines"**
- Beskrivande text under kalendersektion
- Är överflödig/upprepande
- Ta bort, låt kalendern tala för sig själv
---
## 3. Samarbeten (f.d. kampanjer)
**Kampanjer → Samarbete**
- Överallt där "Kampanj" används som term
**Flytta upp så att det är "+ Skapa samarbete" och "Importera avtal" där uppe istället**
- Nuvarande: CTA-knappar längre ner på sidan eller i subnavigation
- Ska vara: Primary actions i toppen av sidan (header-nivå)
**Start och slutdatum för samarbete ändras när man ändrar publiceringsdatum**
- Bugg: När användaren ändrar publiceringsdatum, ändras även start/slutdatum automatiskt
- Ska inte hända - olika fält, oberoende av varandra
- Eller: Ta bort start/slutdatum helt (se nästa punkt)
**Start och slutdatum irrelevant**
- Start/slutdatum är inte viktiga för creators
- Relevant är: Publiceringsdatum (när contentet går live) och Utkastdeadline (när drafts ska skickas)
- Ta bort Start/Slutdatum-fält helt, ersätt med:
- "Publiceringsdatum" (datum)
- "Utkastdeadline" (datum, optional)
**Att det ska stå varumärke / direktkund men inte lägga in som kund**
- Fält för "Varumärke" (t.ex. "H&M") - fritextfält
- Fält för "Byrå" (t.ex. "Swim Communication") - dropdown/autocomplete som skapar Customer-post
- Endast byrå ska finnas i Customers-tabellen
- Varumärke är metadata på samarbetet, inte en relation
**Endast byrå ska läggas in som kund**
- Customer-tabellen innehåller bara byråer (de som faktiskt betalar)
- Varumärken är bara en textsträng på samarbetet
- Fakturan går till byrån, inte varumärket
**Slutkund är ibland varumärke, ibland byrå**
- "Slutkund" = den entitet som ska synas på fakturan och i kommunikation
- Om direktkund (inget mellanled): slutkund = varumärke
- Om via byrå: slutkund = byrå
- Automatisk logik: Om byrå finns → byrå är slutkund. Annars → varumärke är slutkund
**"Skapa ny kund" knapp inte helt intuitiv i ladda upp avtal flödet**
**Byt ut leverabler (svårt ord), extraheras kan också beskrivas lättare**
- "Leverabler" → "Innehåll" eller "Publiceringar"
- "Extraheras" → "Hittades i avtalet" eller "AI fyllde i automatiskt"
- Enklare språk, mindre jargong
**Exklusivitet inte extraherat korrekt (competing brands i x veckor)**
- AI-extrahering hittar inte exklusivitetsklausuler korrekt
- Exempel: "No competing brands for 4 weeks" ska extraheras till "4 veckor"
- Förbättra prompt eller lägg till specifik parsing för exklusivitet
**Lägg till funktion att man kan lägga in exklusivitet själv**
- Om AI missar exklusivitet, ska användaren kunna lägga till manuellt (även där det står "ingen exklusivitet hittades")
- Fält: "Exklusivitet (valfritt)" med dropdown för antal veckor och fritextfält för competing brands
- Eller: Edit-knapp vid extraherad exklusivitet
**Textinnehåll sammanfatta på briefing med AI**
-"Klistra in mailetråd"
- Användaren klistrar in hela mejlkonversationen eller annan text.
- AI extraherar och sammanfattar till strukturerad briefing
**Från kampanj till faktura: populera automatiskt**
- När användaren skapar faktura från ett samarbete
- Auto-populera: Kund (byrå), Belopp (från samarbetsavtalet), Beskrivning (samarbetsnamn), Förfallodatum (publiceringsdatum + 30 dagar)
- Knapp i samarbetsdetaljer: "Skapa faktura" som öppnar fakturaformulär med förifyllda fält
**Fixa UI kundsida**
- Kundsidan (kund-detaljer) har dålig layout/design
- Behöver omarbetas: bättre typografi, spacing, färgschema enligt brand
- Förmodligen legacy-design som inte uppdaterats efter rebrand
---
## 4. Transaktioner & Kategorisering
**Automatiskt fetcha banktransaktioner**
- Enable Banking är redan integrerat men transaktioner hämtas inte automatiskt
- Implementera: Cron job eller webhook som hämtar transaktioner dagligen
- Supabase Edge Function + scheduled trigger för att synka transactions
**Kategorisera logiken lägger av efter 3/6 transaktioner**
- AI-kategorisering fungerar för de första transaktionerna
- Efter 3-6 transaktioner slutar den kategorisera (returnerar null eller default-kategori)
- Förmodligen: Rate limit, token limit, eller bugg i loop-logiken
- Fix: Batch-processa i chunks, kolla error handling
**Undersök balans/resultat??**
- Rapporterad balans/resultat matchar inte faktiskt banksaldo eller förväntad vinst (kanske)
- Förmodligen: Kategoriseringsbuggen ovan gör att många transaktioner blir okategoriserade
- Eller: Dubbelbokföring, felaktig summering
- Fix: Debugga query som beräknar balans, verifiera mot banksaldo
**Gör om swipe funktionen till att man swipear till rätt kostnadskonto istället**
- De mest troliga kostnadskonton visas på skärmen, alternativ för fler om det inte stämmer.
**Lägga till fler alternativ för varje kostnad (ex bankkostnad är inte med)**
- Nuvarande kategorilista är ofullständig
- "Bankkostnad" saknas (avgifter från banken)
- Lägg till: Bankkostnad, Kortavgifter, Valutaväxling, eventuellt fler
- Förmodligen hårdkodad enum, utöka listan
**Faktura ska mappas till transaktioner**
- När en faktura betalas, ska den kopplas till motsvarande banktransaktion
- Auto-matching baserat på: Belopp (±1%), Datum (±7 dagar), Kund-referens
- UI: Ikon som visar "Kopplad till faktura #123" på transaktionen
- Underlättar reconciliation
- Detta kan vara en del av kategoriserings swipe funktionen.
---
## 5. Kalender & Deadlines
**Lägg in så att prel skatte inbetalningsdatum ligger i kalendern**
- Preliminärskatt ska visas som återkommande events i kalendern
- Månatlig (om stor omsättning), kvartalsvis (standard), eller årlig (låg omsättning)
- Automatisk beräkning baserat på ackumulerad vinst
- Event: "Preliminärskatt 12 500 kr" med datum och länk till betalning
**Månadsvis/kvartal/år**
- Intervall för prelskatt beror på förväntad omsättning:
- Månad: >1M kr/år
- Kvartal: 100k-1M kr/år (default)
- År: <100k kr/år
- Automatisk detektering baserat på faktiska intäkter
- Eller: Användarinställning om de vet sin skatteklass
---
## 6. Intäktstyper
**Lägg till "Podcast" som sätt att man får betalt**
- Nuvarande intäktstyper: Instagram, TikTok, YouTube, Sponsrad post, etc.
- Lägg till: "Podcast" (brandintegration i podcast-episoder)
- Ska vara eget val i dropdown "Typ av samarbete" eller "Plattform"
- Samma hantering som andra samarbetstyper: fakturering, kategorisering, momsklassificering
ISSUES:
## Error Type
Runtime SyntaxError
## Error Message
Unexpected token '<'
Next.js version: 16.1.5 (Turbopack)
Fixa så att det är tre cards istället: EF, AB, Frilans (coming soon)
Blir inte automatiskt redirectad till nästa steg efter bank connect:
POST /api/banking/connect 200 in 988ms (compile: 37ms, render: 951ms)
GET /api/banking/callback?state=acdf00c6-82a1-4068-ac07-7510ee41667d&code=061d67b7-81d7-4996-8bb5-9cd731651108 307 in 2.0s (compile: 35ms, render: 2.0s)
GET /onboarding 200 in 167ms (compile: 5ms, proxy.ts: 127ms, render: 35ms)
GET /onboarding 200 in 315ms (compile: 5ms, proxy.ts: 276ms, render: 33ms)
Dessa är på fel ställe och är inkorrekta:
Välkommen till din översikt!
Steg 1 av 5
Stäng guide
Här ser du hela din ekonomiska situation på ett ställe. Låt oss gå igenom de viktigaste delarna.
Fixa namn / bolagsnamn efter God kväll på main page.
Trippla lägg till samarbetsknappar på https://marguerite-nonhieratical-becki.ngrok-free.dev/campaigns
Får ## Error Type
Runtime SyntaxError
## Error Message
Unexpected token '<'
Next.js version: 16.1.5 (Turbopack)
när jag försöker ta bort exklusivitetsmärke från AI extraheringen.
Och detta när jag förösker lägga till exklusivitet manuellt:
## Error Type
Runtime TypeError
## Error Message
Cannot read properties of undefined (reading 'length')
at Step5Exclusivity (components/contracts/ContractImportWizard.tsx:1169:50)
at renderStepContent (components/contracts/ContractImportWizard.tsx:324:16)
at ContractImportWizard (components/contracts/ContractImportWizard.tsx:403:10)
at CampaignImportPage (app/(dashboard)/campaigns/import/page.tsx:37:7)
## Code Frame
1167 | </div>
1168 |
> 1169 | {extraction.exclusivity.excludedBrands.length > 0 && (
| ^
1170 | <div>
1171 | <Label className="mb-2 block">Uteslutna varumärken</Label>
1172 | <div className="flex flex-wrap gap-2">
Next.js version: 16.1.5 (Turbopack)
"Kunde inte kategorisera. Tryck "Hoppa över" för att gå vidare." när jag förösker välja bankkostnader / kortkostnader
POST /api/transactions/d100f6a5-5512-464f-a500-1f63f561e72b/categorize 200 in 1440ms (compile: 474ms, render: 966ms)
Failed to update transaction: {
code: '23514',
details: null,
hint: null,
message: 'new row for relation "transactions" violates check constraint "transactions_category_check"'
}
POST /api/transactions/5d926784-7acb-483c-8859-e34ff29bdd49/categorize 500 in 1045ms (compile: 10ms, render: 1035ms)
Failed to update transaction: {
code: '23514',
details: null,
hint: null,
message: 'new row for relation "transactions" violates check constraint "transactions_category_check"'
}
POST /api/transactions/5d926784-7acb-483c-8859-e34ff29bdd49/categorize 500 in 799ms (compile: 6ms, render: 793ms)
POST /api/transactions/c81e8863-0085-465a-a96b-f3647f28551f/categorize 200 in 1055ms (compile: 6ms, render: 1049ms)
POST /api/transactions/7001bd21-c4e0-46a2-b069-fbb6dc16cd43/categorize 200 in 848ms (compile: 7ms, render: 842ms)
POST /api/transactions/f7dc202a-0938-4614-99cc-254c43e63144/categorize 200 in 902ms (compile: 6ms, render: 896ms)
POST /api/transactions/batch-match-invoices 200 in 425ms (compile: 3ms, render: 422ms)
POST /api/transactions/suggest-categories 200 in 291ms (compile: 1823µs, render: 289ms)
POST /api/transactions/2ebaeaa7-b452-437c-94d3-3716d45085d4/categorize 200 in 836ms (compile: 3ms, render: 833ms)
POST /api/transactions/9bc5e6bb-0dec-4ce0-8732-e6540f44e229/categorize 200 in 860ms (compile: 6ms, render: 853ms)
POST /api/transactions/batch-match-invoices 200 in 124ms (compile: 5ms, render: 119ms)
POST /api/transactions/suggest-categories 200 in 235ms (compile: 2ms, render: 233ms)
Failed to update transaction: {
code: '23514',
details: null,
hint: null,
message: 'new row for relation "transactions" violates check constraint "transactions_category_check"'
}
POST /api/transactions/5d926784-7acb-483c-8859-e34ff29bdd49/categorize 500 in 920ms (compile: 6ms, render: 914ms)
Fixa transaktionslogiken allmänt. Ta bort privatvalet helt.
Undersök om alla intäkter ska vara 3900 övriga rörelseintäkter
Gör en TODO (tex "två passerade deadlines" är direkt under God kväll)
Fixa så titel på samarbete syns.
Värde -> Arvode
Se över bankuppgifter i faktura. Och fakturanr, IBAN, EU momsnmr.
Ta bort skatte "ditt att spendera"
+2192
View File
File diff suppressed because it is too large Load Diff
+469
View File
@@ -0,0 +1,469 @@
1/9
BAS Förenklat årsbokslut (K1) – Kontoplan 2018
Inga ändring eller tillägg har gjorts
jämfört med 2017.
1 Tillgångar
10 Immateriella anläggningstillgångar
BAS-konton Rad Underkonton Rad
1000 Immateriella
anläggningstillgångar
B1 1009 Årets avskrivningar på
immateriella
B1
anläggningstillgångar
11 Byggnader och mark
BAS-konton Rad Underkonton Rad
1110 Byggnader B2 1119 Ackumulerade avskrivningar på
byggnader
B2
1130 Mark B3
1150 Markanläggningar B2 1159 Ackumulerade avskrivningar på
markanläggningar
B2
1180 Pågående nyanläggningar och
förskott för byggnader och
B3
mark
12 Maskiner och inventarier
BAS-konton Underkonton
Rad Rad
1220 Maskiner och inventarier B4 1221 Årets nyanskaffning av
maskiner och inventarier
B4
1222 Årets ersättning för maskiner
och inventarier
B4
1229 Årets avskrivningar på maskiner
och inventarier
B4
1230 Byggnads- och markinventarier B4 1231 Årets nyanskaffning av
byggnads- och markinventarier
B4
1232 Årets ersättning för byggnads-
och markinventarier
B4
1239 Årets avskrivningar på
byggnads- och markinventarier
B4
1240 Bilar och andra transportmedel B4 1241 Årets nyanskaffning av bilar och
andra transportmedel
B4
1242 Årets ersättning för bilar och
andra transportmedel
B4
1249 Årets avskrivningar på bilar och
andra transportmedel
B4
13 Övriga anläggningstillgångar
BAS-konton Underkonton
Rad Rad
1300 Andelar B5
14 Lager
BAS-konton
Underkonton
Rad Rad
Kontoplan_K1_2018_ver1
2/9
1400 Lager B6
15 Kundfordringar
BAS-konton
Underkonton
Rad Rad
1500 Kundfordringar B7
16 Övriga fordringar
BAS-konton Underkonton
Rad Rad
1600 Övriga fordringar B8
1650 Momsfordran B8
17 Förskott till leverantörer
BAS-konton Underkonton
Rad
1700 Förskott till leverantörer B8
19 Kassa och bank
BAS-konton
Underkonton
Rad
1910 Kassa B9
1920 PlusGiro B9
1930 Företagskonto/checkkonto/affär
skonto
B9
1940 Övriga bankkonton B9
1970 Särskilda bankkonton B9
2 EGET KAPITAL OCH SKULDER
20 Eget kapital
BAS-konton Underkonton
Rad Rad
2010 Eget kapital, delägare 1 B10 2011 Egna varuuttag B10
2012 Avräkning för skatter och
avgifter (skattekonto)
B10
2013 Övriga egna uttag B10
2014 Uttag förmåner B10
2017 Egna insättningar B10
2019 Årets resultat, delägare 1 B10
2020 Eget kapital, delägare 2 B10 Se delägre 1
2030 Eget kapital, delägare 3 B10 Se delägre 1
2040 Eget kapital, delägare 4 B10 Se delägre 1
2050 Avsättning till expansionsfond U2
2060 Ersättningsfond U3
2070 Insatsemissioner,
avbetalningsplan på skog,
skogskonto,
U4
2080 Periodiseringsfonder upphovsmannakonto
U1 2083 Periodiseringsfond vid 2012 års
taxering
U1
Kontoplan_K1_2018_ver1
3/9
2084 Periodiseringsfond vid 2013 års
taxering
U1
2085 Periodiseringsfond 2013 U1
2086 Periodiseringsfond 2014 U1
2087 Periodiseringsfond 2015 U1
2088 Periodiseringsfond 2016 U1
2089 Periodiseringsfond 2017 U1
2090 Utjämningskonto upplysningar
1-4
23 Låneskulder
BAS-konton Underkonton
Rad Rad
2330 Checkräkningskredit B13
2350 Skulder till kreditinstitut B13
2390 Övriga låneskulder B13
24 Skulder till leverantörer
BAS-konton
Underkonton
Rad Rad
2440 Leverantörsskulder B15
26 Moms och särskilda punktskatter
BAS-konton Underkonton
Rad Rad
2610 Utgående moms, 25 % B14 2611 Utgående moms på försäljning
inom Sverige, 25 %
B14
2612 Utgående moms på egna uttag,
25 %
B14
2613 Utgående moms för uthyrning,
25 %
B14
2614 Utgående moms omvänd
skattskyldighet, 25 %
B14
2615 Utgående moms import av
varor, 25 %
B14
2618 Vilande utgående moms, 25 % B14
2620 Utgående moms, 12 % B14 2621 Utgående moms på försäljning
inom Sverige, 12 %
B14
2622 Utgående moms på egna uttag,
12 %
B14
2623 Utgående moms för uthyrning,
12 %
B14
2624 Utgående moms omvänd
skattskyldighet, 12 %
B14
2625 Utgående moms import av
varor, 12 %
B14
2628 Vilande utgående moms, 12 % B14
2630 Utgående moms, 6 % B14 2631 Utgående moms på försäljning
inom Sverige, 6 %
B14
2632 Utgående moms på egna uttag,
6 %
B14
Kontoplan_K1_2018_ver1
4/9
2633 Utgående moms för uthyrning,
6 %
B14
2634 Utgående moms omvänd
skattskyldighet, 6 %
B14
2635 Utgående moms import av
varor, 6 %
B14
2638 Vilande utgående moms, 6 % B14
2640 Ingående moms B14 2641 Debiterad ingående moms B14
2642 Debiterad ingående moms i
anslutning till frivillig
B14
2645 Beräknad ingående moms på
skattskyldighet
förvärv från utlandet
B14
2646 Ingående moms på uthyrning B14
2648 Vilande ingående moms B14
2649 Ingående moms, blandad
verksamhet
B14
2650 Redovisningskonto för moms B14 B14
2660 Särskilda punktskatter B14 B14
27 Personalens skatter, avgifter och löneavdrag
BAS-konton Underkonton
Rad Rad
2710 Personalskatt B14
2730 Lagstadgade/avtalade sociala
avgifter och särskild löneskatt
B14
29 Övriga skulder
BAS-konton Underkonton
Rad Rad
2900 Övriga skulder B16
3 RÖRELSENS INKOMSTER/INTÄKTER
30-34 Huvudintäkter
BAS-konton
Underkonton
Rad Rad
3000 Försäljning och utfört arbete
samt övriga momspliktiga
R1
3100 intäkter
Momsfria intäkter R2
3200 Bil- och bostadsförmån m.m. R3
35 Fakturerade kostnader
BAS-konton
Underkonton
Rad Rad
3500 Fakturerade kostnader R1
37 Intäktskorrigeringar
BAS-konton
Underkonton
Rad Rad
3700 Lämnade rabatter, bonus etc. R1/R2
Kontoplan_K1_2018_ver1
5/9
39 Övriga rörelseintäkter
BAS-konton
Underkonton
Rad Rad
3900 Övriga rörelseintäkter R1/R2
3970 Vinst vid avyttring av
immateriella och materiella
R2
3980 anläggningstillgångar
Erhållna bidrag R2
4 UTGIFTER/KOSTNADER FÖR VAROR, MATERIAL OCH VISSA
KÖPTA TJÄNSTER
40-45 Inköp av varor och material
BAS-konton
Underkonton
Rad Rad
4000 Varor R5
46 Legoarbeten, underentreprenader
BAS-konton Underkonton
Rad Rad
4600 Legoarbeten och
underentreprenader
R5
47 Reduktion av inköpspriser
BAS-konton Underkonton
Rad Rad
4700 Erhållna rabatter, bonus etc. R6
49 Förändring av lager
BAS-konton
Underkonton
Rad Rad
4900 Förändring av lager R5
5-6 ÖVRIGA EXTERNA RÖRELSEUTGIFTER/KOSTNADER
50 Lokalkostnader
BAS-konton
Underkonton
Rad Rad
5000 Lokalkostnader R6
51 Fastighetskostnader
BAS-konton
Underkonton
Rad Rad
5100 Fastighetskostnader R6
52 Hyra av anläggningstillgångar
BAS-konton Underkonton
Rad Rad
5200 Hyra av anläggningstillgångar R6
54 Förbrukningsinventarier och förbrukningsmaterial
Kontoplan_K1_2018_ver1
6/9
BAS-konton Underkonton
Rad Rad
5400 Förbrukningsinventarier och
förbrukningsmaterial
R6
55 Reparation och underhåll
BAS-konton
Underkonton
Rad Rad
5500 Reparation och underhåll R6
56 Kostnader för transportmedel
BAS-konton
Underkonton
Rad Rad
5600 Kostnader för transportmedel R6
5610 Personbilskostnader R6 5611 Drivmedel för personbilar R6
5612 Försäkring och skatt för
R6
5613 Reparation och underhåll av
personbilar
personbilar
R6
5615 Leasing av personbilar R6
5618 Schablonmässig milkostnad
privat personbil
R6
5619 Övriga personbilskostnader R6
5620 Lastbilskostnader R6
57 Frakter och transporter
BAS-konton
Underkonton
Rad Rad
5700 Frakter och transporter R6
58 Resekostnader
BAS-konton
Underkonton
Rad Rad
5800 Resekostnader R6
59 Reklam och PR
BAS-konton
Underkonton
Rad Rad
5900 Reklam och PR R6
60 Övriga försäljningskostnader
BAS-konton Underkonton
Rad Rad
6000 Övriga försäljningskostnader
R6
R6 R6
6070 Representation R6 R6 6071 Representation, avdragsgill
6072 Representation, ej avdragsgill R6
+
NE
sid.
2
Kontoplan_K1_2018_ver1
7/9
61 Kontorsmateriel och trycksaker
BAS-konton Underkonton
Rad Rad
6100 Kontorsmateriel och trycksaker R6
62 Tele och post
BAS-konton Underkonton
Rad Rad
6200 Tele och post R6
63 Företagsförsäkringar och övriga riskkostnader
BAS-konton Underkonton
Rad Rad
6300 Företagsförsäkringar och
övriga riskkostnader
R6
6310 Företagsförsäkringar R6
65 Övriga externa tjänster
BAS-konton Underkonton
Rad Rad
6500 Övriga externa tjänster R6
68 Inhyrd personal
BAS-konton Underkonton
Rad Rad
6800 Inhyrd personal R6
69 Övriga kostnader
BAS-konton
Underkonton
Rad Rad
6900 Övriga kostnader R6
6980 Föreningsavgifter R6
7 UTGIFTER/KOSTNADER FÖR PERSONAL, AVSKRIVNINGAR
70 Löner till anställda
BAS-konton Underkonton
Rad Rad
7000 Löner till anställda R7
73 Kostnadsersättningar och förmåner
BAS-konton Underkonton
Rad Rad
7300 Kostnadsersättningar och
förmåner
R7
74 Pensionskostnader
BAS-konton Underkonton
Rad Rad
7400 Pensionskostnader R7
Kontoplan_K1_2018_ver1
8/9
75 Sociala och andra avgifter enligt lag och avtal
BAS-konton Underkonton
Rad Rad
7500 Sociala och andra avgifter
enligt lag och avtal
R7
76 Övriga personalkostnader
BAS-konton Underkonton
Rad Rad
7600 Övriga personalkostnader R7 7631 Personalrepresentation,
avdragsgill
R7
7632 Personalrepresentation, ej
avdragsgill
R7
+
NE
sid.
2
77 Nedskrivningar och återföring av nedskrivningar
BAS-konton Underkonton
Rad Rad
7700 Nedskrivningar R9
alt.
R10
78 Avskrivningar
BAS-konton
Underkonton
Rad Rad
7810 Avskrivningar på immateriella
anläggningstillgångar
R10
7820 Avskrivningar på byggnader
och markanläggningar
R9
7830 Avskrivningar på maskiner och
inventarier
R10
79 Övriga rörelsekostnader
BAS-konton Underkonton
Rad Rad
7970 Förlust vid avyttring av
immateriella och materiella
anläggningstillgångar
R6
7980 Ersättningsfonder R9/
10
8 FINANSIELLA OCH ANDRA INKOMSTER/INTÄKTER OCH
UTGIFTER/ KOSTNADER
83 Övriga ränteintäkter och liknande resultatposter
BAS-konton Underkonton
Rad Rad
Kontoplan_K1_2018_ver1
9/9
8310 Ränteintäkter och utdelningar R4 8314 Skattefria ränteintäkter R4
+
NE
sid.
2
8330 Valutakursdifferenser på
fordringar och placeringar
R4
84 Räntekostnader och liknande
BAS-konton Underkonton
Rad Rad
8410 Räntekostnader för skulder R8
8430 Valutakursdifferenser på
skulder R8
88 Bokslutsdispositioner
89 Årets resultat
BAS-konton Underkonton
Rad Rad
8990 Resultat R11 R11 8999 Årets resultat R11
Kontoplan_K1_2018_ver1
+41
View File
@@ -0,0 +1,41 @@
. Media Kit-generator
Standardverktyg i creator-ekonomin 2025, men inget svenskt verktyg erbjuder det. Kreatörer bygger idag media kits manuellt i Canva.
Implementation: Dra in analytics från Instagram, TikTok, YouTube via API. Generera professionell PDF med demografi, engagemangsgrad, tidigare samarbeten. Inkludera prisförslag baserat på följarantal och engagemang. Automatisk uppdatering kvartalsvis.
Svårighetsgrad: Medel. API-integrationer finns redan delvis i din app.
3. Avtalsmallar och e-signering
Creator-specifika avtal saknas helt på den svenska marknaden. Verktyg som CreatorIQ, RosterGrid och HoneyBook erbjuder detta internationellt. Svenska kreatörer använder inga avtal alls eller kopierar generiska mallar.
Implementation: Mallbibliotek anpassat för svenska influencersamarbeten: leveranser, betalningsvillkor, contenträttigheter, exklusivitet, bytesaffärer. Integrera e-signering (BankID om möjligt). Lagra i kampanjhanteringssystemet.
Svårighetsgrad: Medel. Juridisk granskning krävs men templaten är straightforward.
4. Automatiserade betalningspåminnelser och inkasso-workflow
Ingen kreatör vill jaga betalningar. Det är det näst vanligaste klagomålet efter sena betalningar överhuvudtaget.
Implementation: Automatiska påminnelser vid 15, 30 och 45 dagar. Eskaleringsflöde. Professionell inkassokommunikation. Eventuellt kopplad till fakturafinaliseringen ovan.
Svårighetsgrad: Låg. Primärt e-post/SMS-automation.
5. Multi-currency rate cards och prisriktlinjer
Kreatörer, särskilt nya, underprissätter sig systematiskt. Internationella plattformar erbjuder pricing benchmarks.
Implementation: Föreslå priser baserat på följarantal, engagemang, plattform, nisch. Svenska branschriktlinjer. Visa vad liknande kreatörer tar betalt. Integrera med faktureringsflödet.
Svårighetsgrad: Låg-medel. Datainsamling är huvudutmaningen.
Features att FÖRBÄTTRA
1. AI-assistent: Scenariospecifik, inte generisk
Din kunskapsbas med 30+ scenarion är en stark grund. Men assistenten behöver bli proaktiv, inte reaktiv.
Förbättring: "Du har 3 obetalda fakturor äldre än 30 dagar." "Din barter-deal med X saknar värdering, jag behöver marknadsvärdet." "Du har inte deklarerat periodisk sammanställning för Q3." Assistenten ska agera som en digital revisor som håller koll, inte som en FAQ.
2. Bokföring: "Creator Mode" som default
BAS-kontoplanen är korrekt men onödig för 90% av målgruppen. De behöver inte se konto 2641.
Förbättring: Förenklad vy med creator-kategorier: "Plattformsintäkter", "Samarbeten", "Barter", "Resor", "Utrustning". Full BAS i bakgrunden för SIE-export. Automatisk kategorisering via AI. En-klicks-avstämning för vanliga transaktioner.
3. Bankintegration: Automatisk matchning
PSD2-synk och swipe-kategorisering finns. Saknas: automatisk matchning av banktransaktioner mot fakturor. Flagga transaktioner som kan vara oredovisade intäkter. Kassaflödesprognos baserad på betalningshistorik.
4. Gåvo-/produktspårning: Fullständig skattehantering
Nuvarande feature finns men behöver utökas: automatisk beräkning av marknadsvärde för skatt, spåra om produkt behålls/returneras/ges bort/används som arbetsredskap, generera dokumentation mot Skatteverket, varning när en produkt blir en skattepliktig händelse. Skatteverket granskar influencersektorn aktivt. Det här är compliance-kritiskt.
5. Kampanjhantering: Content-workflow
Lägg till content approval-flöden, varumärkesportal där kunder kan granska leveranser, automatisk spårning av publiceringsschema, prestandamätning per kampanj (räckvidd, engagemang, konverteringar).
Features att ÖVERVÄGA (lägre prioritet)
Affiliate-integration: Många kreatörer tjänar via Adtraction, Awin, Amazon Associates. Auto-import av provisionsdata och skatterapportering. Relevant men inte brådskande.
Kvittomatchning: OCR finns. Lägg till automatisk matchning av kvitto mot banktransaktion. Minskar dubbelinmatning.
Team-access: Kreatörer som skalar anställer assistenter. Begränsade roller (bokförare, assistent, revisor) med auditlogg.
Features att IFRÅGASÄTTA
Fullständigt bokföringsmodulen
De flesta kreatörer behöver inte fullständig BAS-bokföring och kommer ändå outsourca till revisor. Överväg att göra "Exportera till revisor"-paket till huvudflödet och behålla förenklat läge som default. Den avancerade vyn blir opt-in, inte standard.
Kalender som fristående feature
Deadlines är viktiga men kan duplicera verktyg kreatören redan använder. Om användningen är låg, integrera med Google Calendar istället och ta bort egen kalendervy. Deadlines och påminnelser kan leva i appen utan att vara en separat sektion.
+186
View File
@@ -0,0 +1,186 @@
# Influencer Business-in-a-Box MVP
## Quick Context for AI Builders
Financial management SaaS for Swedish influencers/content creators. Supports both enskild firma (sole proprietor) and aktiebolag (limited company). Core problem: tax anxiety and admin overload. Core solution: automated compliance and simplified invoicing.
## Tech Stack
- **Framework**: Next.js 14 (App Router) + TypeScript
- **UI**: Tailwind CSS + shadcn/ui + Framer Motion
- **Database**: Supabase (PostgreSQL with RLS)
- **Auth**: Supabase Auth (magic link)
- **Hosting**: Vercel
- **PDF**: @react-pdf/renderer
- **Banking**: Enable Banking (PSD2 AISP)
- **Currency**: Riksbanken API for exchange rates
## Documentation Index
| Doc | Purpose |
|-----|---------|
| [01-PRD.md](./docs/01-PRD.md) | Product requirements, user stories, success metrics |
| [02-ARCHITECTURE.md](./docs/02-ARCHITECTURE.md) | Technical architecture, PSD2 strategy, security |
| [03-DATABASE-SCHEMA.md](./docs/03-DATABASE-SCHEMA.md) | Full Supabase schema with RLS policies |
| [04-API-SPECIFICATION.md](./docs/04-API-SPECIFICATION.md) | All API endpoints, VAT rules, momsdeklaration rutor |
| [05-UI-SPECIFICATION.md](./docs/05-UI-SPECIFICATION.md) | Wireframes, component specs, design system |
| [06-IMPLEMENTATION-GUIDE.md](./docs/06-IMPLEMENTATION-GUIDE.md) | Step-by-step build guide with code examples |
| [07-FUTURE-FEATURES.md](./docs/07-FUTURE-FEATURES.md) | Phase 2+ features: benefits tracking, NE-bilaga, Skatteverket APIs |
| [08-BAS-ACCOUNTING-GUIDE.md](./docs/08-BAS-ACCOUNTING-GUIDE.md) | BAS account mapping, MCC codes, auto-categorization rules |
## MVP Feature Scope
### Included in MVP
1. **Entity Support**: Both enskild firma and aktiebolag with appropriate tax logic
2. **Bank Integration**: Enable Banking (PSD2) with multiple account support
3. **Tax Dashboard**: "Disponibelt saldo" with F-skatt comparison warning
4. **Transaction Categorization**: Swipe UI with schablonavdrag suggestions
5. **Multi-currency Invoicing**: SEK, EUR, USD with Riksbanken auto-conversion
6. **VAT Automation**: Swedish (25%), EU reverse charge (0%), Export (0%)
7. **Credit Notes**: Kreditfaktura support for invoice corrections
8. **Expense Warnings**: Alert on non-deductible lifestyle costs
9. **Configurable Settings**: Momsperiod (monthly/quarterly), custom invoice numbering
### Deferred to Later
- Receipt photo capture and matching
- Benefits/gifts tracking (förmånshantering)
- NE-bilaga / Årsredovisning auto-generation
- Automated payment reminders
- Quotes/proforma invoices
- English UI
## Entity Type Tax Differences
| Aspect | Enskild Firma | Aktiebolag |
|--------|---------------|------------|
| Profit tax | Egenavgifter (28.97%) + kommunalskatt (~32%) | Bolagsskatt (20.6%) |
| Owner payment | Eget uttag | Lön + utdelning |
| Annual filing | NE-bilaga | Årsredovisning + INK2 |
## Key Swedish Tax & Legal Concepts
| Term | English | Rate/Rule |
|------|---------|-----------|
| Enskild firma | Sole proprietorship | Personal liability, NE-bilaga |
| Aktiebolag (AB) | Limited company | 20.6% bolagsskatt, årsredovisning |
| Egenavgifter | Self-employment contributions | 28.97% (enskild firma only) |
| Arbetsgivaravgifter | Employer contributions | 31.42% (AB paying salary) |
| Bolagsskatt | Corporate tax | 20.6% on AB profit |
| Kommunalskatt | Municipal income tax | ~30-35% varies |
| F-skatt | Business tax registration | Required for invoicing |
| Debiterad preliminärskatt | Preliminary tax set | Monthly F-skatt payment amount |
| Moms | VAT | 25% standard, threshold 80k SEK |
| Momsperiod | VAT reporting period | Monthly or quarterly |
| Räkenskapsår | Fiscal year | Calendar or brutet (broken) |
| Brutet räkenskapsår | Non-calendar fiscal year | e.g., July 1 - June 30 |
| Omvänd skattskyldighet | Reverse charge | EU B2B: buyer reports VAT |
| Kreditfaktura | Credit note | Corrects/cancels issued invoice |
| Schablonavdrag | Standard deduction | Fixed amounts (hemmakontor 2000 kr/år) |
| AGI-deklaration | Employer declaration | Monthly report for AB salaries |
| SIE-fil | Standard Import Export | Accounting data exchange format |
## Entity Type Tax Differences
| Aspect | Enskild Firma | Aktiebolag |
|--------|---------------|------------|
| Profit tax | Egenavgifter (28.97%) + kommunalskatt (~32%) | Bolagsskatt (20.6%) |
| Owner payment | Eget uttag | Lön + utdelning |
| Salary costs | N/A | Arbetsgivaravgifter (31.42%) |
| Annual filing | NE-bilaga | Årsredovisning + INK2 |
| Periodiseringsfond | Max 30% | Max 25% |
**MVP Note:** For AB, system tracks salary payments and calculates arbetsgivaravgifter. Lön/utdelning optimization (3:12-reglerna) deferred to future phase.
## MVP Technical Notes
| Aspect | Implementation |
|--------|----------------|
| Banking | Enable Banking (PSD2 AISP) |
| Email | Supabase built-in |
| Payment on invoices | Bank transfer only (future: third-party) |
| Partial payments | Not supported (paid/unpaid binary) |
| Fiscal year | Calendar + brutet räkenskapsår |
| Multi-user | Single user per account |
| UI Language | Swedish only |
| Data import | SIE file (future feature) |
## Momsdeklaration Ruta Quick Reference
| Ruta | When Used |
|------|-----------|
| 05 | Swedish domestic sales with 25% VAT |
| 39 | EU B2B services (reverse charge) |
| 40 | Export to non-EU |
| 21 | Service purchases from EU (input) |
| 48 | Self-assessed output VAT on foreign purchases |
## Critical Implementation Notes
1. **All tax figures are estimates** - display disclaimer prominently
2. **VIES validation required** for EU reverse charge - without it, charge 25% VAT
3. **Non-custodial design** - app NEVER holds user funds
4. **Multi-currency**: Store original + SEK converted amounts, use Riksbanken rate on invoice date
5. **Invoice numbers**: Allow custom starting number (user may have history elsewhere)
6. **Credit notes**: Must reference original invoice, use negative amounts
7. **Expense warnings** - clothing, cosmetics, gym are NOT deductible per Kammarrätten rulings
8. **7-year retention** - Bokföringslagen requires, GDPR does not override for accounting records
9. **RLS on all tables** - users must only access own data
10. **Swedish UI only** for MVP
## Schablonavdrag Defaults
| Type | Amount | Notes |
|------|--------|-------|
| Hemmakontor | 2 000 kr/år | If dedicated room |
| Bilkostnader | 18.50 kr/mil | Business travel |
| Telefon | Actual or % split | Based on business use |
## Quick Start for Builder AI
1. Read docs in order: PRD → Architecture → Schema → API → UI → Implementation → Future
2. Start with auth + middleware (including onboarding redirect logic)
3. Build onboarding wizard (6 steps, all required except preliminary tax)
4. Implement Enable Banking integration in onboarding step 6
5. Build dashboard with tax calculations (adapt for entity type)
6. Add invoice creation with multi-currency + VIES validation
7. Implement credit note flow
8. Polish UI (Swedish language)
## File Structure
```
/
├── app/
│ ├── (auth)/login/page.tsx
│ ├── (onboarding)/onboarding/ # Wizard steps 1-6
│ ├── (dashboard)/
│ │ ├── page.tsx # Dashboard
│ │ ├── transactions/page.tsx
│ │ ├── invoices/
│ │ │ ├── page.tsx
│ │ │ ├── new/page.tsx
│ │ │ └── [id]/credit/page.tsx # Credit notes
│ │ ├── customers/page.tsx
│ │ └── settings/page.tsx
│ └── api/...
├── components/
│ ├── ui/ # shadcn components
│ ├── onboarding/ # Wizard components
│ ├── dashboard/
│ ├── transactions/
│ └── invoices/
├── lib/
│ ├── supabase/
│ ├── banking/enable-banking.ts
│ ├── currency/riksbanken.ts
│ ├── tax/
│ │ ├── calculator.ts
│ │ ├── expense-warnings.ts
│ │ ├── schablonavdrag.ts
│ │ └── fskatt-warning.ts
│ └── invoice/vat-rules.ts
├── types/index.ts
└── supabase/migrations/
```
File diff suppressed because one or more lines are too long
@@ -0,0 +1,234 @@
# AI-Agent Snabbreferens
## Dokumentöversikt
| Fil | Innehåll | Användning |
|-----|----------|------------|
| 01-karnregler.md | Grundläggande regler, tröskelvärden, bolagsformer | Faktafrågor om regler |
| 02-scenariobibliotek.md | 30 vanliga situationer med lösningar | "Vad gör jag om..." frågor |
| 03-plattformskatalog.md | Plattformsspecifik info (VAT, moms, utbetalningar) | Plattformsfrågor |
| 04-berakningsexempel.md | Konkreta sifferexempel | "Hur beräknar jag..." frågor |
| 05-beslutstrаd.md | Visuella beslutsflöden | Hjälp med val |
| 06-felsokningsguide.md | Problemlösning | "Det stämmer inte..." frågor |
| 07-kalender-deadlines.md | Alla deadlines och påminnelser | Tidsfrågor |
| 08-varningsflaggor.md | Automatiska kontroller och varningar | Proaktiva varningar |
---
## Trigger-ord → Dokument
### Produkter och barter
- "fick produkt", "pressutskick", "gratis", "barter" → Scenario 001-004
- "marknadsvärde", "värdering" → Beräkningsexempel 1.2
### Plattformar
- "YouTube", "AdSense", "Google" → Plattformskatalog (YouTube)
- "Twitch", "subs", "bits" → Plattformskatalog (Twitch)
- "Instagram", "Meta", "Reels bonus" → Plattformskatalog (Instagram)
- "TikTok", "Creator Fund" → Plattformskatalog (TikTok)
- "Patreon" → Plattformskatalog (Patreon)
- "affiliate", "Adtraction" → Plattformskatalog (Affiliate)
### Moms
- "moms", "VAT", "faktura" → Kärnregler sektion 3, Beslutsträd 2.1
- "periodisk sammanställning" → Kärnregler 3.3, Kalender
- "Reverse Charge", "EU" → Scenario 007-010, Beslutsträd 2.1
- "OSS", "privatperson EU" → Kärnregler 3.4, Scenario 018-019
### Bolagsform
- "EF", "enskild firma" → Kärnregler 4.1, Beslutsträd 3.1
- "AB", "aktiebolag" → Kärnregler 4.2, Beslutsträd 3.1
- "byta bolagsform", "starta AB" → Scenario 028, Beslutsträd 3.2
- "utdelning", "3:12" → Scenario 027, Beräkningsexempel 3.1
### Avdrag
- "avdrag", "dra av" → Kärnregler 5, Beslutsträd 4.1
- "kläder", "smink" → Kärnregler 5.2, Scenario 016-017
- "resa", "arbetsresa" → Scenario 005-006, Beslutsträd 1.2
- "hemmakontor", "arbetsrum" → Kärnregler 5.3, Beräkningsexempel 5.2
### Social trygghet
- "SGI", "sjukpenning" → Kärnregler 7, Scenario 023
- "föräldraledighet", "VAB", "barn" → Beräkningsexempel 3.3, Varningsflaggor 3.3
- "pension" → Kärnregler 7.2
### Juridik
- "reklam", "markering", "MFL" → Kärnregler 6, Scenario 024
- "upphovsrätt", "musik", "licens" → Scenario 025, Varningsflaggor 5.2
### Dubbel struktur
- "EF och AB", "båda bolag" → Kärnregler 8, Scenario 029-030
- "internfaktura" → Scenario 030, Varningsflaggor 4.1-4.2
### Deadlines
- "deadline", "när ska", "förfaller" → Kalender
- "momsdeklaration" → Kalender (kvartalsvis/månadsvis)
- "inkomstdeklaration" → Kalender (maj/juli)
### Problem
- "stämmer inte", "fel", "problem" → Felsökningsguide
- "Skatteverket", "granskning" → Felsökningsguide 8
---
## Kritiska tröskelvärden (snabbåtkomst)
| Belopp | Betydelse |
|--------|-----------|
| 1 800 kr | Skattefri tävlingsvinst |
| 2 000 kr | Schablonavdrag arbetsrum EF |
| 5 000 kr | Skattefritt friskvårdsbidrag AB |
| 25 000 kr | Aktiekapital AB |
| 80 000 kr | Momsbefrielse omsättning |
| 99 680 kr | OSS-tröskel EU-privatpersoner |
| 204 325 kr | Schablonbelopp 3:12 (2024) |
| 615 300 kr | Brytpunkt statlig skatt |
| 681 600 kr | Lönekrav för lönebaserat 3:12-utrymme |
---
## Vanliga plattformars momshantering (snabbåtkomst)
| Plattform | Moms | Ruta | Period. samnst. |
|-----------|------|------|-----------------|
| YouTube | Reverse Charge | 39 | Ja |
| Twitch | Export | 40 | Nej |
| Instagram | Reverse Charge | 39 | Ja |
| TikTok | Reverse Charge | 39 | Ja |
| Patreon | Export | 40 | Nej |
| Spotify | Svensk 25% | 05-08 | Nej |
| Adtraction | Svensk 25% | 05-08 | Nej |
---
## Standardsvar-mallar
### Vid fråga om skatteplikt
```
[Inkomsttyp] är [skattepliktig/ej skattepliktig].
Värdering: [Marknadsvärde inkl. moms / Nominellt belopp]
Bokföring:
- Debet [konto]: [belopp]
- Kredit [konto]: [belopp]
[Eventuella villkor eller undantag]
```
### Vid fråga om momshantering
```
Kund: [Kundtyp och land]
Momsregel: [Svensk 25% / Reverse Charge / Export]
Faktura:
- Belopp exkl. moms: [X] kr
- Moms: [Y] kr / 0 kr
- Text på faktura: [Om tillämpligt]
Redovisning:
- Momsdeklaration ruta: [X]
- Periodisk sammanställning: [Ja/Nej]
```
### Vid fråga om avdragsrätt
```
[Kostnadstyp] är [avdragsgill/ej avdragsgill].
Villkor för avdrag:
- [Villkor 1]
- [Villkor 2]
[Om delvis avdragsgill: Beräkning av andel]
Dokumentationskrav:
- [Krav]
```
### Vid varning
```
VARNING: [Typ av varning]
Problem: [Beskrivning]
Konsekvens om ej åtgärdat:
- [Konsekvens 1]
- [Konsekvens 2]
Åtgärd:
1. [Steg 1]
2. [Steg 2]
Deadline: [Om tillämpligt]
```
---
## Konteringsguide (vanliga poster)
### Intäkter
| Typ | Debet | Kredit |
|-----|-------|--------|
| Svensk försäljning | 1510 Kundfordringar | 3010 Intäkter + 2610 Utg. moms |
| EU-försäljning (B2B) | 1510 Kundfordringar | 3011 Försäljning EU |
| Export (utanför EU) | 1510 Kundfordringar | 3012 Försäljning export |
| Kontant plattformsintäkt | 1940 Bank | 3011/3012 beroende på plattform |
| Barter-produkt | 4010 Inköp + 2640 Ing. moms | 3010 Intäkter |
### Kostnader
| Typ | Debet | Kredit |
|-----|-------|--------|
| Inköp med moms | [Kostnadskonto] + 2640 Ing. moms | 1940 Bank / 2440 Lev.skuld |
| Inventarier | 1220 Inventarier + 2640 Ing. moms | 1940 Bank |
| Prenumerationer | 6540 IT-tjänster + 2640 Ing. moms | 1940 Bank |
### Eget uttag (EF)
| Typ | Debet | Kredit |
|-----|-------|--------|
| Privat uttag | 2013 Eget uttag | 1940 Bank |
| Barter till privat bruk | 2013 Eget uttag | 4010 Inköp + 2640 Ing. moms |
### Valutakursdifferenser
| Typ | Debet | Kredit |
|-----|-------|--------|
| Kursvinst | 1940 Bank | 1510 Kundfordringar + 3960 Kursvinst |
| Kursförlust | 1940 Bank + 7960 Kursförlust | 1510 Kundfordringar |
---
## Eskaleringsmatris
| Situation | Åtgärd |
|-----------|--------|
| Enkel faktafråga | Svara direkt från dokumentation |
| Komplex beräkning | Använd beräkningsexempel som mall |
| Osäkerhet om regler | Hänvisa till Skatteverket.se |
| Juridisk tvist | Rekommendera jurist |
| Skatteverket-kontakt | Rekommendera redovisningskonsult |
| Allvarlig varning | Flagga tydligt, föreslå åtgärd |
| Utanför scope | Tydliggör begränsning, hänvisa vidare |
---
## Begränsningar att kommunicera
AI-agenten kan INTE:
- Ge juridisk rådgivning (endast information)
- Garantera skattekonsekvenser
- Ersätta revisor/redovisningskonsult
- Lämna in deklarationer
- Kontakta myndigheter
AI-agenten SKA:
- Ge saklig information baserad på dokumentation
- Flagga risker och varningar
- Rekommendera professionell hjälp vid behov
- Hänvisa till officiella källor
@@ -0,0 +1,219 @@
# Kärnregler: Influencer Ekonomi
## 1. Verksamhetsklassificering
### Tre kumulativa krav för näringsverksamhet
| Krav | Definition | Influencer-exempel |
|------|------------|-------------------|
| Vinstsyfte | Objektiv förväntan på överskott över tid | Affärsplan visar skalbar intäktsmodell |
| Varaktighet | Regelbunden aktivitet, längre period | Daglig/veckovis publicering, löpande samarbeten |
| Självständighet | Egen utrustning, flera kunder, eget risktagande | Egen kamera, 5+ varumärkessamarbeten/år |
### Klassificeringslogik
```
ALLA tre krav uppfyllda → Näringsverksamhet (F-skatt, NE/AB)
EJ alla krav men inkomst finns → Hobbyverksamhet (T2-blankett)
Tillfälliga uppdrag utan F-skatt → Tjänsteinkomst (A-skatt dras av uppdragsgivare)
```
### Hobbyverksamhet
- Beskattas som inkomst av tjänst
- Egenavgifter: 25-29%
- Underskott: Sparas max 5 år, kvittas endast mot framtida hobbyöverskott
- Moms: Registreringsplikt om omsättning > 80 000 kr
---
## 2. Skattepliktiga inkomster
**Grundprincip:** Allt som erhålls på grund av verksamheten är skattepliktigt oavsett form.
### Inkomsttabell
| Typ | Skattepliktigt | Värdering | Undantag |
|-----|----------------|-----------|----------|
| Kontant ersättning | Ja | Nominellt belopp | - |
| Produkter (avtalat samarbete) | Ja | Marknadsvärde inkl. moms | - |
| Pressutskick (oombett) | Villkorat | Marknadsvärde inkl. moms | Returnerad/kasserad utan användning |
| Sponsrad resa | Delvis/helt | Resans faktiska värde | Arbete >30h/vecka, försumbart nöjesinslag |
| Medföljande på resa | Ja | Medföljarens resekostnad | - |
| Donations/Bits/Stars/Subs | Ja | Mottaget belopp | - |
| Affiliate-provision | Ja | Utbetalt belopp | - |
| Rabattkod eget bruk | Ja | Marknadspris minus betalt pris | - |
| Tävlingsvinster | Villkorat | Vinstens värde | ≤1 800 kr eller minnesföremål |
| Virtuella items (skins etc.) | Ja vid försäljning | Försäljningspris | - |
---
## 3. Momsregler
### Tröskelvärden
| Gräns | Belopp | Konsekvens |
|-------|--------|------------|
| Momsbefrielse | 80 000 kr omsättning | Under: Valfri registrering. Över: Obligatorisk. |
| OSS-tröskel | 99 680 kr (10 000 EUR) | Försäljning till EU-privatpersoner |
### Momshantering per kundtyp
| Kund | Plats | Moms på faktura | Redovisning |
|------|-------|-----------------|-------------|
| Svenskt företag | Sverige | 25% | Ruta 05-08 |
| EU-företag | Annat EU-land | 0% (Reverse Charge) | Ruta 39 + Periodisk sammanställning |
| Företag utanför EU | Tredjeland | 0% (Export) | Ruta 40 |
| Privatperson EU | Annat EU-land | Mottagarlandets moms* | OSS-deklaration |
| Privatperson utanför EU | Tredjeland | 0% | Ruta 40 |
*Under 99 680 kr/år: Valfritt svensk moms 25%
### Periodisk sammanställning
- **När:** Kvartalsvis vid försäljning till EU-företag
- **Innehåll:** Köparens VAT-nummer, totalvärde per kund
- **Deadline:** 20:e i månaden efter kvartalets slut
- **Förseningsavgift:** 1 250 kr
- **Kritiskt:** Belopp måste matcha Ruta 39 i momsdeklaration
---
## 4. Bolagsformer
### Jämförelse
| Aspekt | Enskild Firma | Aktiebolag |
|--------|---------------|------------|
| Kapitalinsats | 0 kr | 25 000 kr |
| Ansvar | Personligt, obegränsat | Begränsat till aktiekapital |
| Beskattning | Inkomstskatt + egenavgifter på överskott | Bolagsskatt 20,6% + skatt på lön/utdelning |
| Utdelning 20% skatt | Ej tillgängligt | Ja, upp till gränsbelopp (3:12) |
| Friskvårdsbidrag | Ej avdragsgillt | Skattefritt ≤5 000 kr |
| SGI-grund | Nettoöverskott | Uttagen lön |
| Administration | NE-bilaga | Årsredovisning |
| Rekommenderad vinst | <500 000 kr | >500 000 kr |
### Brytpunkter 2024/2025
| Gräns | Belopp |
|-------|--------|
| Statlig inkomstskatt | 615 300 kr |
| Aktiekapital AB | 25 000 kr |
---
## 5. Avdragsregler
### Avdragsgilla kostnader
| Kategori | Exempel | Villkor |
|----------|---------|---------|
| Teknik | Kamera, dator, mikrofon, ljus, drönare | Yrkesmässig användning |
| Mjukvara | Redigeringsprogram, molntjänster | Verksamhetsrelaterad |
| Agentarvode | Provision till manager/agentur | Direkt kopplad till intäkt |
| Extern lokal | Studiohyra | Exklusiv verksamhetsanvändning |
| Resor | Arbetsresor med dokumenterat syfte | Ej hem-arbete |
### Ej avdragsgilla kostnader
| Kategori | Motivering |
|----------|------------|
| Kläder (normala) | Användbart privat |
| Smink, hudvård | Privat levnadskostnad |
| Skönhetsingrepp | Privat levnadskostnad |
| Accessoarer (väskor, smycken) | Användbart privat |
| Träning, gym | Privat (undantag: friskvård i AB) |
### Arbetsrum i bostad
| Typ | Krav | Avdrag |
|-----|------|--------|
| Faktiskt avdrag | Separat rum, ej bostad, exklusiv användning | Faktisk kostnad |
| Schablon (EF) | >800 h arbete i bostad/år | 2 000 kr/år |
---
## 6. Marknadsföringslagen
### Krav på reklammarkering
| Element | Krav |
|---------|------|
| Placering | Början av inlägg/video |
| Text | "REKLAM" eller "ANNONS" |
| Synlighet | Framträdande färg, storlek |
| Video | Muntligt och/eller textgrafik |
| Sändarangivelse | Vem som betalar ska framgå |
### Överträdelser
- Influencern har eget medverkansansvar
- Förbud vid vite
- Marknadsstörningsavgift
- Skadestånd
### Upphovsrätt
- Licens krävs för musik/material i kommersiella sammanhang
- Referensfall: ABBA-fallet, 200 000 kr ersättning
---
## 7. Social trygghet
### SGI (Sjukpenninggrundande inkomst)
| Bolagsform | SGI baseras på |
|------------|----------------|
| Enskild Firma | Skattemässigt överskott |
| Aktiebolag | Uttagen lön (ej utdelning) |
| Dubbel struktur | Summan av båda |
**Uppbyggnadsskede EF:** Första 36 månaderna kan jämförelseinkomst användas.
### Pension
- Egenföretagare saknar automatisk tjänstepension
- Avdragsgillt sparande: Max 35% av inkomst, max ~573 000 kr/år
- Rekommendation: Spara 4,5-6% av inkomst
---
## 8. Dubbel struktur (EF + AB)
### Användningsområden
| EF används för | AB används för |
|----------------|----------------|
| Småuppdrag <50 000 kr | Större kampanjer >100 000 kr |
| Lågriskuppdrag | Högriskkampanjer |
| Konsultation | Huvudsaklig verksamhet |
| Avvecklingsperiod | Ny huvudverksamhet |
### Interntransaktioner
- Armlängdsprissättning obligatorisk
- Skriftliga avtal krävs
- Moms på internfakturor
- Separata bankkonton och bokföring
### SGI vid dubbel struktur
```
Total SGI = Överskott EF + Lön från AB
```
---
## 9. Kritiska tröskelvärden
| Belopp | Betydelse |
|--------|-----------|
| 1 800 kr | Skattefri tävlingsvinst |
| 2 000 kr | Schablonavdrag arbetsrum EF |
| 5 000 kr | Skattefritt friskvårdsbidrag AB |
| 25 000 kr | Aktiekapital AB |
| 80 000 kr | Momsbefrielse omsättning |
| 99 680 kr | OSS-tröskel EU-privatpersoner |
| 615 300 kr | Brytpunkt statlig skatt |
@@ -0,0 +1,678 @@
# Scenariobibliotek: Vanliga situationer
## Produkter och barter
### SCENARIO 001: Mottagen produkt med samarbetsavtal
**Triggers:** "fick produkt", "samarbete", "ska posta om", "i utbyte mot"
**Situation:** Influencer får produkt värd X kr mot att göra inlägg.
**Beslutsträd:**
```
Finns skriftligt/muntligt avtal om motprestation?
→ Ja → Hela marknadsvärdet inkl. moms är skattepliktig intäkt
```
**Skattemässig hantering:**
- Intäkt: Produktens marknadsvärde inkl. moms
- Om EF: Bokför som intäkt + eget uttag om privat bruk
- Om AB: Bokför som intäkt + förmån om privat bruk
**Bokföringsexempel (EF):**
```
Produkt: Väska värd 8 000 kr inkl. moms
Debet 4010 Inköp varor: 6 400 kr
Debet 2640 Ingående moms: 1 600 kr
Kredit 3010 Intäkter: 8 000 kr
Om privat bruk:
Debet 2013 Eget uttag: 8 000 kr
Kredit 4010 Inköp varor: 6 400 kr
Kredit 2640 Ingående moms: 1 600 kr
```
**Checklista:**
- [ ] Dokumentera marknadsvärde (spara prislapp/screenshot)
- [ ] Spara avtal/mailkonversation
- [ ] Bokför som intäkt
- [ ] Märk inlägg med REKLAM
---
### SCENARIO 002: Oombett pressutskick (behålls)
**Triggers:** "fick skickat", "pressutskick", "utan att fråga", "PR-paket"
**Situation:** Influencer får produkt utan förfrågan, behåller och använder privat.
**Beslutsträd:**
```
Fick du produkten oombedd?
→ Ja
Behåller du den / använder privat?
→ Ja → Skattepliktig intäkt (marknadsvärde)
→ Nej (returnerar/slänger utan användning) → Ej skattepliktig
```
**Skattemässig hantering:**
- Skattepliktig även utan motprestation om produkten behålls
- Värderas till marknadsvärde inkl. moms
**Dokumentationskrav:**
- Fotografera produkten vid mottagande
- Notera avsändare och datum
- Om returnerad: Spara kvitto på returfrakten
---
### SCENARIO 003: Oombett pressutskick (returneras/kastas)
**Triggers:** "skickade tillbaka", "returnerade", "slängde", "gav bort"
**Situation:** Influencer får produkt utan förfrågan, returnerar eller kastar.
**Skattemässig hantering:**
- Ej skattepliktigt om ingen privat användning skett
- Bevisbördan ligger på influencern
**Dokumentationskrav:**
- Kvitto på returfrakten
- Foto av kasserad produkt (om relevant)
- Notering om när och hur produkten avyttrades
**Varning:** Om produkten säljs på "bloppis" blir försäljningsintäkten skattepliktig.
---
### SCENARIO 004: Rabattkod för eget bruk
**Triggers:** "rabattkod", "köpte med rabatt", "fick X% rabatt"
**Situation:** Influencer får personlig rabattkod som del av ersättning.
**Beräkning:**
```
Skattepliktig inkomst = Marknadspris - Faktiskt betalt pris
Exempel:
Marknadspris: 5 000 kr
Betalt: 0 kr
Skattepliktig inkomst: 5 000 kr
```
**Bokföring:** Som intäkt i verksamheten.
---
## Resor och evenemang
### SCENARIO 005: Sponsrad resa (arbetsresa)
**Triggers:** "pressresa", "influencer trip", "betald resa", "bjuden resa"
**Situation:** Influencer bjuds på resa av varumärke.
**Beslutsträd:**
```
Är resan nödvändig för verksamheten?
→ Nej → Helt skattepliktig
→ Ja
Upptar arbete huvudsaklig tid (≥30h/vecka eller ≥6h/dag)?
→ Nej → Delvis/helt skattepliktig (nöjesandel)
→ Ja
Är nöjesinslagen försumbara?
→ Nej → Nöjesandelen skattepliktig
→ Ja → Skattefri
```
**Skattefri resa kräver:**
1. Nödvändig för verksamheten
2. Arbete ≥30 timmar/vecka ELLER ≥6 timmar/dag
3. Nöjesinslag försumbara
**Dokumentationskrav:**
- Detaljerat schema över arbetade timmar
- Leverabler (antal inlägg, stories, videos)
- Avtal som specificerar arbetsuppgifter
---
### SCENARIO 006: Medföljande på resa
**Triggers:** "tog med partner", "min sambo följde med", "vän fick följa med"
**Situation:** Influencerns partner/vän följer med på sponsrad resa kostnadsfritt.
**Skattemässig hantering:**
- Medföljarens resekostnad är skattepliktig förmån för influencern
- Gäller oavsett om influencerns egen resa är skattefri
**Beräkning:**
```
Förmånsvärde = Flygbiljett + Hotell + Eventuella aktiviteter för medföljaren
```
---
## Plattformsintäkter
### SCENARIO 007: YouTube AdSense-utbetalning
**Triggers:** "YouTube betalade", "AdSense", "annonsintäkter YouTube"
**Situation:** Månatlig utbetalning från Google/YouTube.
**Fakta:**
- Utbetalare: Google Ireland Ltd
- VAT-nummer: IE6388047V
- Valuta: USD (normalt)
- Momsregel: Reverse Charge (EU B2B)
**Bokföringsexempel:**
```
Mottaget: $3,200 USD
Valutakurs vid inbetalning: 10.45 SEK/USD
Belopp i SEK: 33,440 kr
Debet 1940 Bank: 33,440 kr
Kredit 3011 Försäljning tjänster EU: 33,440 kr
```
**Momsredovisning:**
- Ruta 39: 33,440 kr
- Periodisk sammanställning: Google Ireland Ltd, IE6388047V, 33,440 kr
---
### SCENARIO 008: Twitch-utbetalning
**Triggers:** "Twitch betalade", "subs", "bits", "Twitch revenue"
**Situation:** Utbetalning från Twitch (prenumerationer, bits, annonser).
**Fakta:**
- Utbetalare: Twitch Interactive Inc
- Säte: USA (San Francisco)
- Momsregel: Export (utanför EU)
**Bokföringsexempel:**
```
Mottaget: $1,500 USD
Valutakurs vid inbetalning: 10.52 SEK/USD
Belopp i SEK: 15,780 kr
Debet 1940 Bank: 15,780 kr
Kredit 3012 Försäljning tjänster utanför EU: 15,780 kr
```
**Momsredovisning:**
- Ruta 40: 15,780 kr
- Ingen periodisk sammanställning (ej EU)
---
### SCENARIO 009: Instagram/Meta bonusar
**Triggers:** "Instagram bonus", "Reels bonus", "Meta betalade"
**Situation:** Bonusutbetalning från Meta för Reels-visningar etc.
**Fakta:**
- Utbetalare: Meta Platforms Ireland Ltd
- VAT-nummer: IE9692928F
- Momsregel: Reverse Charge (EU B2B)
**Hantering:** Samma som YouTube (Scenario 007).
---
### SCENARIO 010: TikTok Creator Fund
**Triggers:** "TikTok betalade", "Creator Fund", "TikTok pengar"
**Situation:** Utbetalning från TikTok Creator Fund.
**Fakta:**
- Utbetalare: TikTok Technology Ltd (ofta)
- VAT-nummer: IE3434547SH
- Säte: Irland
- Momsregel: Reverse Charge (EU B2B)
**OBS:** Kontrollera alltid aktuellt avtal då TikToks struktur varierar.
---
### SCENARIO 011: Donations/Tips (Twitch, YouTube, etc.)
**Triggers:** "donation", "tips", "någon donerade", "fick pengar av tittare"
**Situation:** Tittare skickar pengar direkt via plattform.
**Skattemässig hantering:**
- INTE gåva i skatterättslig mening
- Ersättning för prestation (underhållning)
- Fullt skattepliktig inkomst i näringsverksamheten
**Bokföring:**
```
Debet 1940 Bank: [belopp]
Kredit 3010 Intäkter: [belopp]
```
---
## Affiliate och samarbeten
### SCENARIO 012: Affiliate-provision
**Triggers:** "affiliate", "provision", "trackad länk", "kommission"
**Situation:** Influencer får provision på försäljning via spårningslänk.
**Skattemässig hantering:**
- Fullt skattepliktig intäkt
- Momshantering beror på var affiliate-nätverket är baserat
**Vanliga nätverk och momshantering:**
| Nätverk | Säte | Momsregel |
|---------|------|-----------|
| Amazon Associates | Luxemburg (EU) | Reverse Charge |
| Adtraction | Sverige | 25% moms |
| Tradedoubler | Sverige | 25% moms |
| AWIN | Varierar | Kontrollera avtal |
---
### SCENARIO 013: Ambassadörsavtal med fast månadsersättning
**Triggers:** "ambassadör", "fast ersättning", "månadsarvode", "retainer"
**Situation:** Löpande avtal med fast månadsersättning.
**Skattemässig hantering:**
- Intäktsför månadsvis
- Moms beroende på kundtyp (se momsregler)
- Om svensk kund: Fakturera med 25% moms
**Bokföring:**
```
Månadsersättning: 25,000 kr + moms
Debet 1510 Kundfordringar: 31,250 kr
Kredit 3010 Intäkter: 25,000 kr
Kredit 2610 Utgående moms: 6,250 kr
```
---
## Teknik och utrustning
### SCENARIO 014: Köp av kamera/utrustning
**Triggers:** "köpte kamera", "ny utrustning", "investerade i teknik"
**Situation:** Inköp av professionell utrustning.
**Beslutsträd:**
```
Används utrustningen uteslutande i verksamheten?
→ Ja → Fullt avdrag + momsavdrag
→ Nej (även privat bruk)
→ Proportionera avdrag efter verksamhetsandel
→ Professionell utrustning: Ofta godtas helavdrag ändå
```
**Bokföringsexempel (kamera 45,000 kr inkl. moms):**
```
Debet 1220 Inventarier: 36,000 kr
Debet 2640 Ingående moms: 9,000 kr
Kredit 1940 Bank: 45,000 kr
```
**Avskrivning:**
- Datorer, kameror: Normalt 3-5 år
- Möjlighet till direktavdrag om <25,900 kr (halvt prisbasbelopp)
---
### SCENARIO 015: Prenumerationer och mjukvara
**Triggers:** "Adobe", "prenumeration", "programvara", "app-kostnad"
**Situation:** Löpande kostnader för mjukvara.
**Vanliga avdragsgilla prenumerationer:**
- Adobe Creative Cloud
- Final Cut Pro
- Canva Pro
- Notion, Airtable
- Molnlagring (Google Drive, Dropbox)
- Schemaläggningsverktyg (Later, Buffer)
**Bokföring:** Kostnadsförs löpande som förbrukningsmaterial eller extern tjänst.
---
## Kläder och utseende
### SCENARIO 016: Köp av kläder för content
**Triggers:** "köpte kläder", "outfit för video", "kläder till inlägg"
**Situation:** Influencer köper kläder att visa i content.
**Huvudregel:** Ej avdragsgillt om kläderna kan användas privat.
**Undantag (avdragsgilla):**
- Uniformer
- Skyddskläder
- Extrema scenkläder (ej användbara privat)
- Specifik rekvisita (t.ex. historiska kostymer)
**Dokumentationskrav för undantag:**
- Fotografera plagget
- Motivera varför det ej är användbart privat
- Bevisbördan ligger på influencern
---
### SCENARIO 017: Skönhetsbehandlingar
**Triggers:** "filler", "botox", "behandling", "skönhetsingrepp"
**Situation:** Influencer gör estetisk behandling.
**Skattemässig hantering:**
- Aldrig avdragsgillt
- Klassas som privat levnadskostnad
- Gäller även om utseendet är "del av varumärket"
---
## Försäljning och merchandise
### SCENARIO 018: Försäljning av egen merch till svenska kunder
**Triggers:** "säljer merch", "egen kollektion", "t-shirts"
**Situation:** Försäljning av varor till svenska privatpersoner.
**Skattemässig hantering:**
- Intäkt i verksamheten
- 25% moms på försäljningen
- Inköpskostnad för varorna avdragsgill
**Bokföringsexempel:**
```
Såld t-shirt: 299 kr inkl. moms
Debet 1940 Bank: 299 kr
Kredit 3010 Försäljning varor: 239,20 kr
Kredit 2610 Utgående moms: 59,80 kr
```
---
### SCENARIO 019: Försäljning av merch till EU-kunder
**Triggers:** "kund i Tyskland", "säljer till EU", "europeisk kund"
**Situation:** Försäljning av varor till privatpersoner i annat EU-land.
**Beslutsträd:**
```
Total EU-privatpersonförsäljning under året:
≤99,680 kr → Valfritt: Svensk moms 25% ELLER mottagarlandets moms
>99,680 kr → Obligatoriskt: Mottagarlandets moms via OSS
```
**OSS (One Stop Shop):**
- Registrering via Skatteverket
- Kvartalsvis deklaration
- Samlar all utländsk EU-moms på ett ställe
---
### SCENARIO 020: Försäljning av virtuella items (gaming)
**Triggers:** "sålde skins", "CS:GO", "in-game items"
**Situation:** Försäljning av virtuella föremål för riktiga pengar.
**Beslutsträd:**
```
Sker försäljning yrkesmässigt och regelbundet?
→ Ja → Näringsverksamhet (inkomst av näring)
→ Nej (enstaka försäljningar) → Inkomst av kapital (30% på vinsten)
```
**OBS:** Gränsdragningen är svår. Vid regelbunden handel, behandla som näringsverksamhet.
---
## Valuta och internationellt
### SCENARIO 021: Valutakursdifferens
**Triggers:** "kurs ändrades", "växlingskurs", "valutaförlust"
**Situation:** Skillnad mellan fakturakurs och betalningskurs.
**Beräkning:**
```
Fakturerat: $1,000 @ 10.20 = 10,200 kr
Mottaget: $1,000 @ 10.45 = 10,450 kr
Valutakursvinst: 250 kr
```
**Bokföring:**
```
Valutakursvinst:
Debet 1940 Bank: 10,450 kr
Kredit 1510 Kundfordringar: 10,200 kr
Kredit 3960 Valutakursvinster: 250 kr
Valutakursförlust:
Debet 1940 Bank: 9,950 kr
Debet 7960 Valutakursförluster: 250 kr
Kredit 1510 Kundfordringar: 10,200 kr
```
---
## Sociala avgifter och trygghet
### SCENARIO 022: Uppdrag utan F-skatt
**Triggers:** "har inte F-skatt", "de drog skatt", "fick lön istället"
**Situation:** Influencer utför uppdrag men saknar F-skatt.
**Konsekvenser:**
- Uppdragsgivaren ska dra A-skatt (~30%)
- Uppdragsgivaren betalar arbetsgivaravgifter (31,42%)
- Ersättningen behandlas som lön
**Checklista:**
- [ ] Begär kontrolluppgift från uppdragsgivaren
- [ ] Kontrollera att skatt faktiskt dragits
- [ ] Deklarera som inkomst av tjänst
---
### SCENARIO 023: Beräkna SGI-konsekvens av utdelning vs lön
**Triggers:** "sjukpenning", "VAB", "föräldraledighet", "SGI"
**Situation:** Val mellan lön och utdelning påverkar socialförsäkring.
**Beräkning:**
```
Alt 1: Lön 400,000 kr
SGI: 400,000 kr
Sjukpenning (80%): 320,000 kr/år
Alt 2: Lön 100,000 kr + Utdelning 300,000 kr
SGI: 100,000 kr
Sjukpenning (80%): 80,000 kr/år
Skillnad vid 6 månaders sjukskrivning: 120,000 kr
```
---
## Juridik och avtal
### SCENARIO 024: Glömt reklammarkering
**Triggers:** "glömde skriva reklam", "markerade inte", "fick klagomål"
**Situation:** Inlägg publicerades utan korrekt reklammarkering.
**Åtgärder:**
1. Redigera inlägget omedelbart - lägg till "REKLAM" överst
2. Dokumentera när rättelsen gjordes
3. Informera uppdragsgivaren
**Risker:**
- Varning från Konsumentverket
- Förbud vid vite
- Marknadsstörningsavgift
- Reputationsskada
---
### SCENARIO 025: Använt musik utan licens
**Triggers:** "använde låt", "bakgrundsmusik", "copyright claim"
**Situation:** Musik användes i kommersiellt content utan tillstånd.
**Konsekvenser:**
- Upphovsrättsintrång
- Skadeståndsskyldighet
- Referens: ABBA-fallet (200,000 kr)
**Förebyggande:**
- Använd royalty-free musik
- Köp licens via Epidemic Sound, Artlist etc.
- Kontrollera rättigheter innan publicering
---
## Bokslut och deklaration
### SCENARIO 026: Periodiseringsfond (EF)
**Triggers:** "skjuta upp skatt", "periodiseringsfond", "jämna ut resultat"
**Situation:** Influencer vill jämna ut beskattning över år.
**Regler:**
- Max 30% av överskottet kan avsättas
- Återförs senast år 6
- Vid avveckling av EF: Omedelbar återföring
**Exempel:**
```
År 1: Överskott 500,000 kr
Avsättning (30%): 150,000 kr
Beskattas: 350,000 kr
År 3: Överskott 100,000 kr
Återföring: 150,000 kr
Beskattas: 250,000 kr
```
---
### SCENARIO 027: 3:12-utdelning (AB)
**Triggers:** "utdelning", "gränsbelopp", "20% skatt"
**Situation:** Ägare vill ta ut lågbeskattad utdelning från AB.
**Gränsbelopp 2024:**
- Schablonbelopp: 204,325 kr (2,75 x IBB)
- ELLER lönebaserat utrymme (kräver minst 681,600 kr i egen lön)
**Strategi:**
1. Ta ut lön upp till brytpunkt statlig skatt (~615,300 kr)
2. Ta ut utdelning inom gränsbelopp (20% skatt)
3. Spara överskjutande i bolaget eller ta som lön
---
### SCENARIO 028: Byta från EF till AB
**Triggers:** "vill byta", "starta AB", "övergång"
**Situation:** Influencer vill övergå från EF till AB.
**Process:**
1. Starta AB (25,000 kr aktiekapital)
2. Inkråmsöverlåtelse: EF säljer tillgångar till AB
3. Nya avtal tecknas med AB som part
4. Uppdatera plattformar med nytt VAT-nummer
5. Avveckla EF
**Skattekonsekvenser:**
- Inkråmsöverlåtelse till marknadsvärde
- Moms på försäljningen
- Periodiseringsfonder i EF återförs
- Realisationsvinst i EF beskattas
---
## Dubbel struktur
### SCENARIO 029: Fördela uppdrag mellan EF och AB
**Triggers:** "vilket företag", "ska jag fakturera från", "EF eller AB"
**Situation:** Influencer har både EF och AB, nytt uppdrag inkommer.
**Beslutsmatris:**
| Faktor | → EF | → AB |
|--------|------|------|
| Ersättning <50,000 kr | ✓ | |
| Ersättning >100,000 kr | | ✓ |
| Hög juridisk risk | | ✓ |
| Engångsuppdrag | ✓ | |
| Långt ambassadörskap | | ✓ |
| Behöver bygga SGI | ✓ | |
---
### SCENARIO 030: Internfaktura mellan EF och AB
**Triggers:** "fakturera mig själv", "mellan bolagen", "internfaktura"
**Situation:** EF utför tjänst åt eget AB.
**Krav:**
- Armlängdsprissättning (marknadsmässigt pris)
- Skriftligt avtal
- Affärsmässig motivering
- Moms på fakturan (25%)
**Bokföringsexempel:**
```
EF fakturerar AB för videoproduktion: 20,000 kr + moms
I EF:
Debet 1510 Kundfordringar: 25,000 kr
Kredit 3010 Intäkter: 20,000 kr
Kredit 2610 Utgående moms: 5,000 kr
I AB:
Debet 4010 Köpta tjänster: 20,000 kr
Debet 2640 Ingående moms: 5,000 kr
Kredit 2440 Leverantörsskulder: 25,000 kr
```
**Varning:** Skatteverket granskar internprissättning. Avvikelse >20% från marknad flaggas.
@@ -0,0 +1,398 @@
# Plattformskatalog
## YouTube (Google AdSense)
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | Google Ireland Ltd |
| VAT-nummer | IE6388047V |
| Adress | Gordon House, Barrow Street, Dublin 4, Ireland |
| Säte | Irland (EU) |
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Reverse Charge |
| Svensk moms på faktura | Nej (0%) |
| Momsdeklaration ruta | 39 |
| Periodisk sammanställning | Ja, kvartalsvis |
### Utbetalningar
| Parameter | Värde |
|-----------|-------|
| Valuta | USD |
| Utbetalningströskel | $100 |
| Utbetalningsfrekvens | Månadsvis (21:a-26:e) |
| Utbetalningsmetod | Banköverföring, check |
### Var hittar jag underlag?
1. YouTube Studio → Analytics → Revenue
2. AdSense → Payments → Transaction history
3. Ladda ner CSV för bokföringsunderlag
### Bokföringskontering
```
Intäkt från YouTube:
Debet 1940 Bank: [SEK-belopp]
Kredit 3011 Försäljning tjänster EU: [SEK-belopp]
```
---
## Twitch
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | Twitch Interactive Inc |
| VAT-nummer | Inget (USA) |
| Adress | 350 Bush Street, 2nd Floor, San Francisco, CA 94104, USA |
| Säte | USA (utanför EU) |
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Export |
| Svensk moms på faktura | Nej (0%) |
| Momsdeklaration ruta | 40 |
| Periodisk sammanställning | Nej |
### Utbetalningar
| Parameter | Värde |
|-----------|-------|
| Valuta | USD |
| Utbetalningströskel | $50 (Affiliate), $100 (Partner) |
| Utbetalningsfrekvens | 15:e varje månad (för föregående månad) |
| Net terms | 45 dagar |
### Intäktsströmmar
| Typ | Beskrivning |
|-----|-------------|
| Subscriptions | Prenumerationer (50% till streamer normalt) |
| Bits | Virtuell valuta från tittare |
| Ads | Annonsintäkter |
| Bounties | Sponsrade kampanjer via Twitch |
### Var hittar jag underlag?
1. Creator Dashboard → Channel Analytics → Revenue
2. Payout History för historiska utbetalningar
3. Tax Interview måste fyllas i (W-8BEN)
### Bokföringskontering
```
Intäkt från Twitch:
Debet 1940 Bank: [SEK-belopp]
Kredit 3012 Försäljning tjänster utanför EU: [SEK-belopp]
```
---
## Instagram / Meta
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | Meta Platforms Ireland Ltd |
| VAT-nummer | IE9692928F |
| Adress | 4 Grand Canal Square, Grand Canal Harbour, Dublin 2, Ireland |
| Säte | Irland (EU) |
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Reverse Charge |
| Svensk moms på faktura | Nej (0%) |
| Momsdeklaration ruta | 39 |
| Periodisk sammanställning | Ja, kvartalsvis |
### Utbetalningar (Bonusprogram)
| Parameter | Värde |
|-----------|-------|
| Valuta | USD eller EUR |
| Utbetalningströskel | $100 |
| Utbetalningsfrekvens | Månadsvis |
| Utbetalningsmetod | Banköverföring, PayPal |
### Intäktsströmmar
| Typ | Beskrivning |
|-----|-------------|
| Reels Bonus | Prestationsbaserat på Reels-visningar |
| Badges | Tittare köper badges under Live |
| Subscriptions | Prenumerationer på exklusivt innehåll |
| Branded Content | Märkta samarbeten (betalning från varumärke) |
### Var hittar jag underlag?
1. Professional Dashboard → Monetization → Payouts
2. Meta Business Suite → Insights → Monetization
3. Payout Settings för bankuppgifter och skatteinfo
### Bokföringskontering
```
Intäkt från Instagram/Meta:
Debet 1940 Bank: [SEK-belopp]
Kredit 3011 Försäljning tjänster EU: [SEK-belopp]
```
---
## TikTok
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | TikTok Technology Ltd (EU) |
| VAT-nummer | IE3434547SH |
| Adress | 10 Earlsfort Terrace, Dublin 2, D02 T380, Ireland |
| Säte | Irland (EU) |
**OBS:** TikToks struktur varierar. Kontrollera alltid avtalet för korrekt entitet.
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Reverse Charge |
| Svensk moms på faktura | Nej (0%) |
| Momsdeklaration ruta | 39 |
| Periodisk sammanställning | Ja, kvartalsvis |
### Utbetalningar
| Parameter | Värde |
|-----------|-------|
| Valuta | USD |
| Utbetalningströskel | $10 (Creator Fund) |
| Utbetalningsfrekvens | Månadsvis |
### Intäktsströmmar
| Typ | Beskrivning |
|-----|-------------|
| Creator Fund | Prestationsbaserat på visningar |
| Creator Rewards | Förbättrat program ersätter Creator Fund |
| LIVE Gifts | Virtuella gåvor under livestream |
| Series | Betalinnehåll |
### Var hittar jag underlag?
1. TikTok Creator Portal → Balance
2. Transaction History för detaljer
3. Tax Information måste fyllas i
---
## Patreon
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | Patreon Inc |
| VAT-nummer | Inget (USA) |
| Adress | 600 Townsend Street, Suite 500, San Francisco, CA 94103, USA |
| Säte | USA (utanför EU) |
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Export |
| Svensk moms på faktura | Nej (0%) |
| Momsdeklaration ruta | 40 |
| Periodisk sammanställning | Nej |
### Utbetalningar
| Parameter | Värde |
|-----------|-------|
| Valuta | USD |
| Utbetalningströskel | $25 |
| Utbetalningsfrekvens | Månadsvis (1:a-5:e) |
| Avgifter | 5-12% plattformsavgift + betalningsavgifter |
### Var hittar jag underlag?
1. Creator Dashboard → Income
2. Payout History med CSV-export
3. Tax Settings för W-8BEN
---
## Spotify (Podcasters)
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | Spotify AB |
| VAT-nummer | SE556703748501 |
| Adress | Regeringsgatan 19, 111 53 Stockholm, Sweden |
| Säte | Sverige |
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Svensk inrikes |
| Svensk moms på faktura | Ja (25%) |
| Momsdeklaration ruta | 05-08 |
| Periodisk sammanställning | Nej |
### Bokföringskontering
```
Intäkt från Spotify:
Debet 1940 Bank: [totalbelopp]
Kredit 3010 Försäljning tjänster Sverige: [nettobelopp]
Kredit 2610 Utgående moms: [moms 25%]
```
---
## Amazon Associates (Affiliate)
### Företagsinformation
| Fält | Värde |
|------|-------|
| Juridiskt namn | Amazon EU S.à r.l. |
| VAT-nummer | LU26375245 |
| Adress | 38 avenue John F. Kennedy, L-1855 Luxembourg |
| Säte | Luxemburg (EU) |
**OBS:** Amazon har flera entiteter. Kontrollera vilket program/land som är aktuellt.
### Momshantering
| Regel | Värde |
|-------|-------|
| Momstyp | Reverse Charge |
| Svensk moms på faktura | Nej (0%) |
| Momsdeklaration ruta | 39 |
| Periodisk sammanställning | Ja, kvartalsvis |
### Utbetalningar
| Parameter | Värde |
|-----------|-------|
| Valuta | EUR/USD beroende på program |
| Utbetalningströskel | €25/£25/$10 |
| Utbetalningsfrekvens | ~60 dagar efter månadslut |
---
## Svenska affiliate-nätverk
### Adtraction
| Fält | Värde |
|------|-------|
| Juridiskt namn | Adtraction Marketing AB |
| Organisationsnummer | 556748-8855 |
| VAT-nummer | SE556748885501 |
| Säte | Sverige |
| Moms | 25% svensk moms |
### Tradedoubler
| Fält | Värde |
|------|-------|
| Juridiskt namn | Tradedoubler AB |
| Organisationsnummer | 556575-7423 |
| VAT-nummer | SE556575742301 |
| Säte | Sverige |
| Moms | 25% svensk moms |
### Awin (Nordics)
| Fält | Värde |
|------|-------|
| Juridiskt namn | AWIN AG (filial i UK ofta) |
| Säte | Varierar |
| Moms | Kontrollera avtal |
---
## Sammanfattningstabell
| Plattform | Säte | VAT-nummer | Momsregel | Ruta | Period. sammanst. |
|-----------|------|------------|-----------|------|-------------------|
| YouTube | Irland | IE6388047V | Reverse Charge | 39 | Ja |
| Twitch | USA | - | Export | 40 | Nej |
| Instagram/Meta | Irland | IE9692928F | Reverse Charge | 39 | Ja |
| TikTok | Irland | IE3434547SH | Reverse Charge | 39 | Ja |
| Patreon | USA | - | Export | 40 | Nej |
| Spotify | Sverige | SE556703748501 | Svensk 25% | 05-08 | Nej |
| Amazon EU | Luxemburg | LU26375245 | Reverse Charge | 39 | Ja |
| Adtraction | Sverige | SE556748885501 | Svensk 25% | 05-08 | Nej |
| Tradedoubler | Sverige | SE556575742301 | Svensk 25% | 05-08 | Nej |
---
## Valutakurser
### Huvudregel
Använd valutakursen den dag inbetalningen sker till bankkontot.
### Kurskällor
- Riksbankens dagskurser (officiell)
- Bankens faktiska växlingskurs (om tillgänglig)
### Praktisk hantering
```
1. Notera belopp i originalvaluta
2. Notera inbetalningsdatum
3. Hämta kurs för det datumet
4. Beräkna SEK-belopp
5. Bokför i SEK
6. Vid fakturadatum ≠ betalningsdatum: Bokför kursdifferens
```
---
## Skatteformulär per plattform
| Plattform | Formulär | Beskrivning |
|-----------|----------|-------------|
| YouTube/Google | W-8BEN | US tax withholding |
| Twitch | W-8BEN | US tax withholding |
| TikTok | W-8BEN | US tax withholding |
| Patreon | W-8BEN | US tax withholding |
| Meta | Tax Information | I plattformen |
**W-8BEN:** Formulär som intygar att du inte är amerikansk skattskyldig. Utan detta hålls 30% amerikansk skatt inne.
---
## Utbetalningskalender (typisk)
| Plattform | Intjäningsmånad | Utbetalning |
|-----------|-----------------|-------------|
| YouTube | Januari | 21-26 februari |
| Twitch | Januari | 15 mars |
| Instagram | Januari | Februari (varierar) |
| TikTok | Januari | Februari (varierar) |
| Patreon | Januari | 1-5 februari |
| Amazon Associates | Januari | Mars (60 dagar) |
@@ -0,0 +1,601 @@
# Beräkningsexempel
## 1. Inkomstberäkningar
### 1.1 YouTube-utbetalning med valutaomräkning
**Scenario:** Influencer får månatlig AdSense-utbetalning.
```
Uppgifter:
- Utbetalt: $2,847.63 USD
- Inbetalningsdatum: 2024-02-23
- Valutakurs (Riksbanken): 10.3842 SEK/USD
Beräkning:
$2,847.63 × 10.3842 = 29,571.87 SEK
Bokföring:
Debet 1940 Bank: 29,571.87 kr
Kredit 3011 Försäljning tjänster EU: 29,571.87 kr
Momsdeklaration:
Ruta 39: 29,571.87 kr
Periodisk sammanställning:
Kund: Google Ireland Ltd
VAT: IE6388047V
Belopp: 29,571.87 kr
```
---
### 1.2 Barter-produkt med skatteberäkning
**Scenario:** Influencer får väska värd 12,000 kr mot ett Instagram-inlägg.
```
Uppgifter:
- Produkt: Designerväska
- Marknadsvärde: 12,000 kr inkl. moms
- Bolagsform: Enskild firma
Beräkning skattepliktig inkomst:
Intäkt: 12,000 kr
Om privat bruk (eget uttag):
Nettovärde: 12,000 / 1.25 = 9,600 kr
Moms: 2,400 kr
Bokföring (produkt mottagen):
Debet 4010 Inköp varor: 9,600 kr
Debet 2640 Ingående moms: 2,400 kr
Kredit 3010 Intäkter: 12,000 kr
Bokföring (eget uttag för privat bruk):
Debet 2013 Eget uttag: 12,000 kr
Kredit 4010 Inköp varor: 9,600 kr
Kredit 2640 Ingående moms: 2,400 kr
Skattekonsekvens (antagen marginalskatt 35%):
Skatt på 12,000 kr: ~4,200 kr
```
---
### 1.3 Sponsrad resa med medföljande
**Scenario:** Influencer bjuds på resa, partner följer med gratis.
```
Uppgifter:
- Flyg influencer: 8,500 kr
- Hotell influencer (5 nätter): 15,000 kr
- Flyg medföljande: 8,500 kr
- Hotell medföljande: ingår i dubbelrum
- Aktiviteter (båda): 4,000 kr
- Arbetade timmar: 35 timmar under 5 dagar (7h/dag)
- Dokumenterat arbete: 12 Instagram-inlägg, 4 Stories/dag, 2 Reels
Bedömning influencerns resa:
- Arbete: 35 timmar = 7 timmar/dag ✓
- Kriteriet "huvudsaklig arbetstid" uppfyllt
- Skattefri om nöjesinslag försumbara
Medföljandes resa:
- Alltid skattepliktig förmån
Beräkning förmånsvärde medföljande:
Flyg: 8,500 kr
Hotell: 0 kr (ingen merkostnad för dubbelrum i detta fall)
Aktiviteter (50%): 2,000 kr
Totalt förmånsvärde: 10,500 kr
Skattekonsekvens (marginalskatt 35%):
Skatt på 10,500 kr: ~3,675 kr
```
---
### 1.4 Donations/Tips från Twitch
**Scenario:** Streamer får donations under månad.
```
Uppgifter:
- Bits mottagna: 45,000 bits
- Bits-värde: $0.01 per bit = $450
- Direktdonationer via PayPal: $320
- Subscriptions (50% av $4.99 × 85 subs): $212.08
- Valutakurs: 10.55 SEK/USD
Beräkning total intäkt:
Bits: $450 × 10.55 = 4,747.50 kr
Donations: $320 × 10.55 = 3,376.00 kr
Subs: $212.08 × 10.55 = 2,237.44 kr
Totalt: 10,360.94 kr
Bokföring (Twitch-utbetalning från USA):
Debet 1940 Bank: 10,360.94 kr
Kredit 3012 Försäljning tjänster utanför EU: 10,360.94 kr
Momsdeklaration:
Ruta 40: 10,360.94 kr
```
---
## 2. Momsberäkningar
### 2.1 Faktura till svenskt företag
**Scenario:** Samarbete med svenskt klädmärke.
```
Uppgifter:
- Arvode: 35,000 kr exkl. moms
- Influencer har F-skatt och är momsregistrerad
Fakturabelopp:
Arvode: 35,000 kr
Moms 25%: 8,750 kr
Totalt att betala: 43,750 kr
Bokföring vid fakturering:
Debet 1510 Kundfordringar: 43,750 kr
Kredit 3010 Intäkter: 35,000 kr
Kredit 2610 Utgående moms: 8,750 kr
Bokföring vid betalning:
Debet 1940 Bank: 43,750 kr
Kredit 1510 Kundfordringar: 43,750 kr
```
---
### 2.2 Faktura till EU-företag (Reverse Charge)
**Scenario:** Samarbete med tyskt företag.
```
Uppgifter:
- Arvode: €3,000 EUR
- Valutakurs vid fakturadatum: 11.25 SEK/EUR
- Kundens VAT-nummer: DE123456789
Fakturabelopp:
Arvode: €3,000 (33,750 SEK)
Moms: €0 (Reverse Charge)
Totalt: €3,000
Obligatorisk text på faktura:
"Reverse charge - VAT to be accounted for by the recipient"
"Art. 196 Council Directive 2006/112/EC"
Bokföring vid fakturering:
Debet 1510 Kundfordringar: 33,750 kr
Kredit 3011 Försäljning tjänster EU: 33,750 kr
Momsdeklaration:
Ruta 39: 33,750 kr
Periodisk sammanställning:
Land: DE
VAT-nummer: DE123456789
Belopp: 33,750 kr
```
---
### 2.3 Merch-försäljning till EU-privatperson
**Scenario:** Säljer t-shirt till kund i Finland.
```
Uppgifter:
- T-shirt pris: 349 kr inkl. moms
- Kund: Privatperson i Finland
- Total EU-privatpersonförsäljning hittills i år: 45,000 kr
Bedömning:
Under OSS-tröskel (99,680 kr) → Kan välja svensk moms
Alternativ 1: Svensk moms
Pris: 349 kr inkl. 25% svensk moms
Netto: 279.20 kr
Moms: 69.80 kr
Alternativ 2: Finsk moms (24%)
Netto: 349 / 1.24 = 281.45 kr
Moms: 67.55 kr
Redovisas via OSS
Vid överskridande av tröskel:
- Obligatoriskt mottagarlandets moms
- Registrera för OSS hos Skatteverket
```
---
### 2.4 Kvartalsvis periodisk sammanställning
**Scenario:** Sammanställning Q1.
```
EU-försäljning under Q1:
- Google Ireland Ltd (IE6388047V): 87,450 kr
- Meta Platforms Ireland Ltd (IE9692928F): 12,300 kr
- TikTok Technology Ltd (IE3434547SH): 8,200 kr
- Tyskt varumärke GmbH (DE987654321): 25,000 kr
Periodisk sammanställning:
┌─────────────────────────────────────────────────────┐
│ Period: 2024-01-01 till 2024-03-31 │
├──────────┬────────────────┬─────────────────────────┤
│ Land │ VAT-nummer │ Belopp (SEK) │
├──────────┼────────────────┼─────────────────────────┤
│ IE │ IE6388047V │ 87,450 │
│ IE │ IE9692928F │ 12,300 │
│ IE │ IE3434547SH │ 8,200 │
│ DE │ DE987654321 │ 25,000 │
├──────────┴────────────────┼─────────────────────────┤
│ SUMMA │ 132,950 │
└───────────────────────────┴─────────────────────────┘
Kontrollavstämning:
Ruta 39 i momsdeklarationen Q1: 132,950 kr ✓
```
---
## 3. Skatteoptimering
### 3.1 Lön vs utdelning i AB
**Scenario:** AB med vinst 800,000 kr före lön.
```
Förutsättningar:
- Vinst före lön: 800,000 kr
- Gränsbelopp för lågbeskattad utdelning: 204,325 kr
- Brytpunkt statlig skatt: 615,300 kr
Alternativ 1: Endast lön 800,000 kr
Arbetsgivaravgifter (31.42%): 251,360 kr
Maximal lönekostnad: 800,000 kr
Bruttolön: 800,000 / 1.3142 = 608,719 kr
Inkomstskatt (~32%): 194,790 kr
Netto till ägaren: 413,929 kr
SGI: 608,719 kr ✓
Alternativ 2: Lön 500,000 kr + Utdelning
Lön brutto: 500,000 kr
Arbetsgivaravgifter: 157,100 kr
Total lönekostnad: 657,100 kr
Kvar i bolaget: 142,900 kr
Bolagsskatt (20.6%): 29,438 kr
Tillgängligt för utdelning: 113,462 kr
Utdelningsskatt (20%): 22,692 kr
Netto utdelning: 90,770 kr
Lön efter skatt (~32%): 340,000 kr
Total netto: 430,770 kr
SGI: 500,000 kr ✓
Alternativ 3: Lön till brytpunkt + Max utdelning
Lön brutto: 615,300 kr
Arbetsgivaravgifter: 193,327 kr
Total lönekostnad: 808,627 kr
→ Överstiger vinst, ej genomförbart
Rekommendation:
Alternativ 2 ger högst netto OCH god SGI.
```
---
### 3.2 EF: Periodiseringsfond
**Scenario:** Enskild firma med ojämna resultat.
```
År 1: Överskott 600,000 kr (viralt år)
År 2: Överskott 150,000 kr (normalt år)
År 3: Överskott 100,000 kr (svagt år)
Utan periodiseringsfond:
År 1 skatt (antagen 45% marginal på del): ~200,000 kr
År 2 skatt (~32%): ~48,000 kr
År 3 skatt (~32%): ~32,000 kr
Total skatt: ~280,000 kr
Med periodiseringsfond:
År 1:
Överskott: 600,000 kr
Avsättning (30%): 180,000 kr
Beskattas: 420,000 kr
Skatt (~40% snitt): 168,000 kr
År 2:
Överskott: 150,000 kr
Avsättning (30%): 45,000 kr
Beskattas: 105,000 kr
Skatt (~32%): 33,600 kr
År 3:
Överskott: 100,000 kr
Återföring: 225,000 kr (180k + 45k)
Beskattas: 325,000 kr
Skatt (~38% snitt): 123,500 kr
Total skatt: ~325,100 kr
Analys:
I detta fall blev det dyrare med periodiseringsfond!
Fonden lönar sig när framtida inkomster förväntas vara LÄGRE.
```
---
### 3.3 SGI-optimering
**Scenario:** Influencer planerar barn, vill maximera föräldrapenning.
```
Nuvarande struktur (AB):
- Lön: 25,000 kr/månad = 300,000 kr/år
- Utdelning: 400,000 kr/år
- SGI: 300,000 kr
Föräldrapenning vid SGI 300,000 kr:
Dagpenning (80%): 657 kr/dag (tak)
→ SGI under taket 599,250 kr, får 80% av faktisk SGI
Faktisk dagpenning: 300,000 × 0.8 / 365 = 657 kr/dag
Optimerad struktur (12 månader före barn):
- Lön: 50,000 kr/månad = 600,000 kr/år
- Utdelning: 100,000 kr/år
- SGI: 600,000 kr
Föräldrapenning vid SGI 600,000 kr:
Dagpenning (80%): 1,315 kr/dag
Men tak finns: 1,116 kr/dag (2024)
Beräkning per 390 dagar föräldraledighet:
Låg SGI (300k): 657 × 390 = 256,230 kr
Hög SGI (600k): 1,116 × 390 = 435,240 kr
Skillnad: 179,010 kr mer vid optimerad SGI
Kostnad för omställning:
Ökad lön 300,000 kr/år → ökad skatt ~100,000 kr
Nettofördel efter 1 års planering: ~79,000 kr
```
---
## 4. Valutaberäkningar
### 4.1 Kursdifferens vid försenad betalning
**Scenario:** Faktura i EUR med betalning 45 dagar senare.
```
Fakturadatum: 2024-01-15
Fakturabelopp: €5,000
Kurs 2024-01-15: 11.35 SEK/EUR
Fakturerat i SEK: 56,750 kr
Betalningsdatum: 2024-03-01
Kurs 2024-03-01: 11.52 SEK/EUR
Mottaget i SEK: 57,600 kr
Kursdifferens: 57,600 - 56,750 = 850 kr (vinst)
Bokföring vid fakturering:
Debet 1510 Kundfordringar: 56,750 kr
Kredit 3011 Försäljning tjänster EU: 56,750 kr
Bokföring vid betalning:
Debet 1940 Bank: 57,600 kr
Kredit 1510 Kundfordringar: 56,750 kr
Kredit 3960 Valutakursvinster: 850 kr
```
---
### 4.2 Aggregerad månadsintäkt från flera plattformar
**Scenario:** Månadssammanställning med blandade valutor.
```
Januari 2024 intäkter:
YouTube (USD):
Belopp: $3,245.00
Inbetalning: 2024-02-22
Kurs: 10.42
SEK: 33,812.90
Twitch (USD):
Belopp: $1,876.50
Inbetalning: 2024-02-15
Kurs: 10.38
SEK: 19,477.67
TikTok (USD):
Belopp: $456.00
Inbetalning: 2024-02-28
Kurs: 10.45
SEK: 4,765.20
Adtraction (SEK):
Belopp: 8,450 kr
SEK: 8,450.00
Total intäkt januari: 66,505.77 kr
Momsredovisning:
Ruta 39 (EU - YouTube, TikTok): 38,578.10 kr
Ruta 40 (Export - Twitch): 19,477.67 kr
Ruta 05-08 (Sverige - Adtraction): 6,760 kr netto + 1,690 kr moms
```
---
## 5. Avdragsberäkningar
### 5.1 Datorutrustning med blandad användning
**Scenario:** Köp av MacBook Pro för video och privat.
```
Uppgifter:
- Inköpspris: 35,000 kr inkl. moms
- Bedömd verksamhetsanvändning: 80%
- Bolagsform: Enskild firma
Beräkning avdrag:
Nettopris: 35,000 / 1.25 = 28,000 kr
Moms: 7,000 kr
Verksamhetsandel 80%:
Avdragsgill kostnad: 28,000 × 0.80 = 22,400 kr
Avdragsgill moms: 7,000 × 0.80 = 5,600 kr
Bokföring:
Debet 1220 Inventarier: 22,400 kr
Debet 2640 Ingående moms: 5,600 kr
Debet 2013 Eget uttag: 7,000 kr
Kredit 1940 Bank: 35,000 kr
Avskrivning (5 år):
Årligt avdrag: 22,400 / 5 = 4,480 kr
```
---
### 5.2 Hemmakontor (schablon vs faktiskt)
**Scenario:** Influencer arbetar i lägenhet.
```
Uppgifter:
- Total hyra: 12,000 kr/månad
- Lägenhetens yta: 75 kvm
- Arbetsrummets yta: 12 kvm
- Arbetade timmar hemma: 1,200 timmar/år
Alternativ 1: Schablonavdrag
Krav: >800 timmar/år ✓
Avdrag: 2,000 kr/år
Alternativ 2: Faktiskt avdrag (om kraven uppfylls)
Krav: Separat rum, exklusiv verksamhetsanvändning
Andel: 12/75 = 16%
Årshyra: 12,000 × 12 = 144,000 kr
Avdrag: 144,000 × 0.16 = 23,040 kr
Skillnad: 21,040 kr/år
VARNING: Faktiskt avdrag kräver att rummet INTE används
för boende. Kraven är mycket stränga. Vid tveksamhet,
använd schablon eller hyr extern lokal.
```
---
### 5.3 Resa med delvis privat syfte
**Scenario:** Resa till New York för samarbete + privat.
```
Uppgifter:
- Totalt 7 dagar
- Arbetsdagar: 4 dagar (möten, inspelning)
- Privata dagar: 3 dagar (sightseeing)
- Flygbiljett: 9,500 kr
- Hotell: 3,000 kr/natt × 7 = 21,000 kr
- Traktamente utland (USA): 614 kr/dag
Beräkning avdragsgill del:
Flyg: 9,500 × (4/7) = 5,429 kr
Hotell arbetsdagar: 3,000 × 4 = 12,000 kr
Traktamente arbetsdagar: 614 × 4 = 2,456 kr
Totalt avdragsgillt: 19,885 kr
Ej avdragsgillt (privat): 9,500 - 5,429 + 9,000 = 13,071 kr
```
---
## 6. Jämförande beräkningar
### 6.1 EF vs AB vid olika vinstnivåer
**Scenario:** Jämförelse vid 400k, 600k och 1M kr vinst.
```
Vinst 400,000 kr:
EF:
Egenavgifter (~28%): 112,000 kr
Skattepliktig inkomst: 288,000 kr
Inkomstskatt (~32%): 92,160 kr
Netto: 195,840 kr
AB (lön 300k + utdelning):
Lön brutto: 300,000 kr
Arbetsgivaravgifter: 94,260 kr
Kvar: 5,740 kr
Bolagsskatt: 1,182 kr
Lön efter skatt: 204,000 kr
Utdelning: 4,558 kr × 0.8 = 3,646 kr
Netto: 207,646 kr
Fördel AB: 11,806 kr
---
Vinst 600,000 kr:
EF:
Egenavgifter: 168,000 kr
Skattepliktig: 432,000 kr
Inkomstskatt (~35%): 151,200 kr
Netto: 280,800 kr
AB (lön 400k + utdelning):
Lön brutto: 400,000 kr
Arbetsgivaravgifter: 125,680 kr
Kvar: 74,320 kr
Bolagsskatt: 15,310 kr
Lön efter skatt: 272,000 kr
Utdelning: 59,010 kr × 0.8 = 47,208 kr
Netto: 319,208 kr
Fördel AB: 38,408 kr
---
Vinst 1,000,000 kr:
EF:
Egenavgifter: 280,000 kr
Skattepliktig: 720,000 kr
Inkomstskatt (~42%): 302,400 kr
Netto: 417,600 kr
AB (lön 600k + utdelning):
Lön brutto: 600,000 kr
Arbetsgivaravgifter: 188,520 kr
Kvar: 211,480 kr
Bolagsskatt: 43,565 kr
Lön efter skatt: 390,000 kr
Utdelning: 167,915 kr × 0.8 = 134,332 kr
Netto: 524,332 kr
Fördel AB: 106,732 kr
```
**Slutsats:** AB blir mer fördelaktigt ju högre vinsten är.
Brytpunkt ligger omkring 300-400k kr vinst.
@@ -0,0 +1,554 @@
# Beslutsträd
## 1. Produkter och förmåner
### 1.1 Mottagen produkt
```
┌─ Fick du en produkt?
│
├─► Fanns avtal/överenskommelse om motprestation?
│ │
│ ├─► JA (samarbete)
│ │ └─► SKATTEPLIKTIG INTÄKT
│ │ Värde: Marknadspris inkl. moms
│ │ Bokför som: Intäkt + eget uttag (om privat bruk)
│ │
│ └─► NEJ (oombett pressutskick)
│ │
│ ├─► Behåller du produkten?
│ │ │
│ │ ├─► JA
│ │ │ └─► SKATTEPLIKTIG INTÄKT
│ │ │ Värde: Marknadspris inkl. moms
│ │ │
│ │ └─► NEJ
│ │ │
│ │ ├─► Returnerade
│ │ │ └─► EJ SKATTEPLIKTIG
│ │ │ Krav: Spara returkvitto
│ │ │
│ │ ├─► Kastade/gav bort
│ │ │ └─► EJ SKATTEPLIKTIG
│ │ │ Krav: Ingen privat användning dessförinnan
│ │ │
│ │ └─► Sålde vidare
│ │ └─► FÖRSÄLJNINGSINTÄKT SKATTEPLIKTIG
│ │ (Ej ursprungsvärdet, utan säljpriset)
```
---
### 1.2 Sponsrad resa
```
┌─ Bjuden på resa av uppdragsgivare?
│
├─► Är resan nödvändig för verksamheten?
│ │
│ ├─► NEJ
│ │ └─► HELA VÄRDET SKATTEPLIKTIGT
│ │
│ └─► JA
│ │
│ ├─► Arbetar du ≥30h/vecka ELLER ≥6h/dag under resan?
│ │ │
│ │ ├─► NEJ
│ │ │ └─► DELVIS SKATTEPLIKTIGT
│ │ │ Nöjesandelen beskattas
│ │ │
│ │ └─► JA
│ │ │
│ │ ├─► Är nöjesinslagen försumbara?
│ │ │ │
│ │ │ ├─► JA
│ │ │ │ └─► SKATTEFRI RESA
│ │ │ │
│ │ │ └─► NEJ
│ │ │ └─► DELVIS SKATTEPLIKTIGT
│ │ │ Nöjesandelen beskattas
│
├─► Följer partner/vän med gratis?
│ └─► JA
│ └─► ALLTID SKATTEPLIKTIG FÖRMÅN
│ Värde: Medföljarens faktiska reskostnad
```
---
### 1.3 Rabattkod/personalrabatt
```
┌─ Fick du rabatt som del av ersättning?
│
├─► Rabatt på produkter du ska marknadsföra?
│ └─► SKATTEPLIKTIG
│ Värde: Marknadspris - betalt pris
│
├─► Rabatt för eget bruk (ej kopplat till uppdrag)?
│ │
│ ├─► Erbjuds samma rabatt till allmänheten?
│ │ └─► EJ SKATTEPLIKTIG (öppen kampanj)
│ │
│ └─► Exklusiv rabatt?
│ └─► SKATTEPLIKTIG
│ Värde: Normalrabatt till allmänheten jämförs
```
---
## 2. Momshantering
### 2.1 Val av momsregel vid fakturering
```
┌─ Vem fakturerar du?
│
├─► Svenskt företag
│ └─► SVENSK MOMS 25%
│ Ruta: 05-08
│ Periodisk sammanställning: Nej
│
├─► Företag i annat EU-land
│ │
│ ├─► Har kunden giltigt VAT-nummer?
│ │ │
│ │ ├─► JA
│ │ │ └─► REVERSE CHARGE (0% moms)
│ │ │ Ruta: 39
│ │ │ Periodisk sammanställning: JA
│ │ │ Text på faktura: "Reverse charge"
│ │ │
│ │ └─► NEJ
│ │ └─► Behandla som privatperson
│ │ → Se "Privatperson i EU"
│ │
├─► Företag utanför EU
│ └─► EXPORT (0% moms)
│ Ruta: 40
│ Periodisk sammanställning: Nej
│
├─► Privatperson i Sverige
│ └─► SVENSK MOMS 25%
│ Ruta: 05-08
│
├─► Privatperson i annat EU-land
│ │
│ ├─► Total EU-privatförsäljning ≤99,680 kr/år?
│ │ │
│ │ ├─► JA
│ │ │ └─► VALFRITT:
│ │ │ • Svensk moms 25% (enklast)
│ │ │ • ELLER mottagarlandets moms via OSS
│ │ │
│ │ └─► NEJ (över tröskeln)
│ │ └─► OBLIGATORISKT mottagarlandets moms
│ │ Redovisa via OSS
│ │
├─► Privatperson utanför EU
│ └─► EXPORT (0% moms)
│ Ruta: 40
```
---
### 2.2 Momsregistrering
```
┌─ Behöver jag momsregistrera mig?
│
├─► Bedriver du momspliktig verksamhet?
│ │
│ ├─► NEJ (t.ex. sjukvård, utbildning)
│ │ └─► INGEN MOMSREGISTRERING
│ │
│ └─► JA
│ │
│ ├─► Omsättning >80,000 kr/år?
│ │ │
│ │ ├─► JA
│ │ │ └─► OBLIGATORISK registrering
│ │ │
│ │ └─► NEJ
│ │ │
│ │ ├─► Säljer du till EU-företag (Reverse Charge)?
│ │ │ │
│ │ │ ├─► JA
│ │ │ │ └─► REKOMMENDERAD registrering
│ │ │ │ (för periodisk sammanställning)
│ │ │ │
│ │ │ └─► NEJ
│ │ │ └─► FRIVILLIG registrering
│ │ │ Fördel: Momsavdrag på inköp
```
---
## 3. Bolagsform
### 3.1 EF eller AB?
```
┌─ Vilken bolagsform passar?
│
├─► Förväntad årlig vinst?
│ │
│ ├─► <300,000 kr
│ │ └─► ENSKILD FIRMA
│ │ • Enklare administration
│ │ • Skattemässigt likvärdigt
│ │
│ ├─► 300,000 - 500,000 kr
│ │ │
│ │ ├─► Accepterar du personligt ansvar?
│ │ │ │
│ │ │ ├─► JA
│ │ │ │ └─► EF kan fungera
│ │ │ │ Överväg AB om växande
│ │ │ │
│ │ │ └─► NEJ
│ │ │ └─► AKTIEBOLAG
│ │ │
│ ├─► >500,000 kr
│ │ └─► AKTIEBOLAG
│ │ • Betydande skattefördel via 3:12
│ │ • Ansvarsbegränsning
│ │
├─► Juridisk risk i verksamheten?
│ │
│ ├─► HÖG (kontroversiellt innehåll, upphovsrättskänsligt)
│ │ └─► AKTIEBOLAG
│ │ Personlig ekonomi skyddad
│ │
│ └─► LÅG
│ └─► Båda fungerar
│
├─► Planerar investerare/partners?
│ │
│ ├─► JA
│ │ └─► AKTIEBOLAG
│ │ Ägarandelar möjliga
│ │
│ └─► NEJ
│ └─► Båda fungerar
```
---
### 3.2 När byta från EF till AB?
```
┌─ Ska jag byta till AB?
│
├─► Vinst >500,000 kr konsekvent (2+ år)?
│ └─► JA → Stark indikation för AB
│
├─► Juridisk tvist eller risk?
│ └─► JA → AB skyddar privatekonomin
│
├─► Vill ha utdelning istället för lön?
│ └─► JA → AB möjliggör 20% skatt
│
├─► Vill anställa personal?
│ └─► JA → AB ofta lämpligare
│
├─► Planerar sälja verksamheten?
│ └─► JA → AB enklare att överlåta
│
Om 2+ JA → Överväg starkt att byta till AB
```
---
## 4. Avdrag
### 4.1 Är kostnaden avdragsgill?
```
┌─ Är utgiften avdragsgill?
│
├─► Teknik (kamera, dator, mikrofon, ljus)
│ │
│ ├─► Används i verksamheten?
│ │ │
│ │ ├─► Uteslutande verksamhet
│ │ │ └─► FULLT AVDRAG
│ │ │
│ │ ├─► Delvis privat
│ │ │ └─► PROPORTIONELLT AVDRAG
│ │ │ (Bedöm verksamhetsandel)
│ │ │
│ │ └─► Huvudsakligen privat
│ │ └─► INGET/MARGINELLT AVDRAG
│
├─► Kläder
│ │
│ ├─► Kan användas privat?
│ │ │
│ │ ├─► JA (normala kläder)
│ │ │ └─► EJ AVDRAGSGILL
│ │ │
│ │ └─► NEJ
│ │ │
│ │ ├─► Uniform/skyddskläder
│ │ │ └─► AVDRAGSGILL
│ │ │
│ │ ├─► Extrem scenklädsel
│ │ │ └─► AVDRAGSGILL
│ │ │ (Dokumentera varför ej privat)
│ │ │
│ │ └─► Historisk kostym/rekvisita
│ │ └─► AVDRAGSGILL
│
├─► Smink och skönhet
│ │
│ ├─► Vardagssmink/hudvård
│ │ └─► EJ AVDRAGSGILL
│ │
│ ├─► Teatersmink/sfx
│ │ └─► AVDRAGSGILL
│ │
│ └─► Skönhetsingrepp (filler, botox, kirurgi)
│ └─► EJ AVDRAGSGILL
│ (Oavsett "varumärkes"-argument)
│
├─► Resa
│ │
│ ├─► Arbetsresa med dokumenterat syfte?
│ │ │
│ │ ├─► JA, endast arbete
│ │ │ └─► FULLT AVDRAG
│ │ │
│ │ ├─► JA, men även privata inslag
│ │ │ └─► PROPORTIONELLT AVDRAG
│ │ │ (Endast arbetsdagar)
│ │ │
│ │ └─► Huvudsakligen privat
│ │ └─► EJ AVDRAGSGILL
│ │
│ └─► Pendling hem-arbete
│ └─► EJ AVDRAGSGILL
│
├─► Hemmakontor
│ │
│ ├─► Separat rum, ej bostad, exklusiv användning?
│ │ │
│ │ ├─► JA
│ │ │ └─► FAKTISKT AVDRAG
│ │ │ (Andel av hyra)
│ │ │
│ │ └─► NEJ
│ │ └─► SCHABLONAVDRAG 2,000 kr
│ │ (Krav: >800 h arbete i bostad)
│
├─► Agentarvode/provision
│ └─► FULLT AVDRAG
│ (Direkt kopplat till intäkt)
│
├─► Programvara/prenumerationer
│ │
│ ├─► Verksamhetsrelaterad?
│ │ │
│ │ ├─► JA (Adobe, redigeringsprogram)
│ │ │ └─► FULLT AVDRAG
│ │ │
│ │ └─► NEJ (Netflix, Spotify privat)
│ │ └─► EJ AVDRAGSGILL
```
---
## 5. Social trygghet
### 5.1 SGI-optimering
```
┌─ Hur optimerar jag SGI?
│
├─► Bolagsform?
│ │
│ ├─► Enskild firma
│ │ │
│ │ ├─► SGI = skattemässigt överskott
│ │ │
│ │ ├─► Gör du avdrag som sänker överskottet?
│ │ │ │
│ │ │ ├─► JA
│ │ │ │ └─► VARNING: Lägre SGI!
│ │ │ │ Överväg om avdragen är värda
│ │ │ │ den lägre sjuk-/föräldrapenningen
│ │ │ │
│ │ │ └─► NEJ
│ │ │ └─► OK
│ │ │
│ │ └─► Uppbyggnadsskede (<36 månader)?
│ │ └─► JA → Kan få jämförelseinkomst
│ │
│ └─► Aktiebolag
│ │
│ ├─► SGI = endast utbetald lön
│ │ (Utdelning räknas EJ)
│ │
│ ├─► Tar du ut tillräcklig lön?
│ │ │
│ │ ├─► JA (≥400,000 kr/år rekommenderat)
│ │ │ └─► OK
│ │ │
│ │ └─► NEJ (låg lön + hög utdelning)
│ │ └─► VARNING: Lågt skydd!
│ │ Risk vid sjukdom/barn
│
├─► Planerar du barn inom 12 månader?
│ │
│ └─► JA
│ └─► MAXIMERA SGI NU
│ • EF: Minimera avdrag
│ • AB: Höj lönen, sänk utdelning
│ • SGI tar ~12 månader att bygga upp
│
├─► Befintligt SGI-skydd tillräckligt?
│ │
│ ├─► Test: Vad får du i sjukpenning?
│ │ Sjukpenning = SGI × 0.8 / 365 per dag
│ │
│ └─► Rekommendation: SGI ≥400,000 kr
│ → ~876 kr/dag i sjukpenning
```
---
## 6. Marknadsföringslagen
### 6.1 Ska inlägget märkas som reklam?
```
┌─ Behöver jag märka med REKLAM?
│
├─► Fick du ersättning (pengar, produkter, tjänster)?
│ │
│ ├─► JA
│ │ │
│ │ ├─► Finns avtal/överenskommelse om publicering?
│ │ │ │
│ │ │ ├─► JA
│ │ │ │ └─► MÄRKNING KRÄVS
│ │ │ │ "REKLAM" eller "ANNONS"
│ │ │ │ Placering: Överst i inlägg
│ │ │ │
│ │ │ └─► NEJ (spontan recension)
│ │ │ │
│ │ │ ├─► Behöll du produkten?
│ │ │ │ │
│ │ │ │ ├─► JA
│ │ │ │ │ └─► REKOMMENDERAD märkning
│ │ │ │ │ (Transparens)
│ │ │ │ │
│ │ │ │ └─► NEJ
│ │ │ │ └─► Ingen märkning krävs
│ │ │
│ └─► NEJ (köpte själv, äkta rekommendation)
│ │
│ ├─► Affiliate-länk inkluderad?
│ │ │
│ │ ├─► JA
│ │ │ └─► MÄRKNING KRÄVS
│ │ │ (Du tjänar på klick/köp)
│ │ │
│ │ └─► NEJ
│ │ └─► Ingen märkning krävs
│
├─► Är det osäkert?
│ └─► JA
│ └─► MÄRK ÄNDÅ
│ Hellre för tydlig än otydlig
```
---
## 7. Dubbel struktur (EF + AB)
### 7.1 Vilket företag för uppdraget?
```
┌─ Ska uppdraget faktureras från EF eller AB?
│
├─► Ersättningsnivå?
│ │
│ ├─► <50,000 kr
│ │ └─► EF (enkelhet)
│ │
│ ├─► 50,000 - 100,000 kr
│ │ └─► Bedöm övriga faktorer ↓
│ │
│ └─► >100,000 kr
│ └─► AB (skatteoptimering)
│
├─► Juridisk risk?
│ │
│ ├─► Hög (kontroversiellt, oklara rättigheter)
│ │ └─► AB (ansvarsbegränsning)
│ │
│ └─► Låg
│ └─► Båda fungerar
│
├─► Avtalstyp?
│ │
│ ├─► Engångsuppdrag
│ │ └─► EF (enkelhet)
│ │
│ └─► Långt ambassadörskap (6+ månader)
│ └─► AB (stabilare struktur)
│
├─► Internationell klient?
│ │
│ ├─► JA
│ │ └─► AB (tydligare för utländska parter)
│ │
│ └─► NEJ
│ └─► Båda fungerar
│
├─► Behöver bygga SGI?
│ │
│ ├─► JA, via EF-överskott
│ │ └─► EF
│ │
│ ├─► JA, via AB-lön
│ │ └─► AB
│ │
│ └─► SGI tillräcklig
│ └─► Optimera för skatt
```
---
### 7.2 Internfaktura mellan EF och AB
```
┌─ Ska EF fakturera AB (eller vice versa)?
│
├─► Finns verklig tjänst/vara som levereras?
│ │
│ ├─► JA
│ │ │
│ │ ├─► Är prissättningen marknadsmässig?
│ │ │ │
│ │ │ ├─► JA (inom ±20% av marknad)
│ │ │ │ │
│ │ │ │ ├─► Finns skriftligt avtal?
│ │ │ │ │ │
│ │ │ │ │ ├─► JA
│ │ │ │ │ │ └─► OK - Internfaktura tillåten
│ │ │ │ │ │ Glöm ej moms (25%)
│ │ │ │ │ │
│ │ │ │ │ └─► NEJ
│ │ │ │ │ └─► UPPRÄTTA AVTAL
│ │ │ │ │ Innan fakturering
│ │ │ │ │
│ │ │ └─► NEJ (avviker >20%)
│ │ │ └─► JUSTERA PRISET
│ │ │ Risk: Omklassificering av Skatteverket
│ │ │
│ └─► NEJ (ren skatteplanering)
│ └─► EJ TILLÅTEN
│ Risk: Förtäckt lön/utdelning
```
@@ -0,0 +1,500 @@
# Felsökningsguide
## 1. Momsproblem
### 1.1 Momsen stämmer inte
**Symptom:** Momsdeklarationen balanserar inte eller Skatteverket ifrågasätter.
**Kontrollpunkter:**
```
□ Har du separerat inhemsk/EU/export-försäljning korrekt?
→ Inhemsk (25%): Ruta 05-08
→ EU B2B (0%): Ruta 39
→ Export (0%): Ruta 40
□ Matchar periodisk sammanställning Ruta 39?
→ Summorna MÅSTE vara identiska
→ Kontrollera att alla EU-kunder finns med
□ Har du bokfört barter-produkter med moms?
→ Marknadsvärde INKL moms som intäkt
→ Momsandel ska redovisas
□ Finns valutakursdifferenser som påverkar?
→ Kursen vid fakturering vs betalning
→ Differenser bokförs separat
□ Har du blandat B2B och B2C inom EU?
→ B2B: Reverse Charge
→ B2C: OSS eller svensk moms
```
**Åtgärder:**
1. Exportera alla försäljningsfakturor
2. Kategorisera per momstyp
3. Summera per kategori
4. Jämför med momsdeklaration
5. Korrigera differenser
---
### 1.2 Periodisk sammanställning saknas eller är fel
**Symptom:** Förseningsavgift eller ifrågasättande från Skatteverket.
**Kontrollpunkter:**
```
□ Har du sålt tjänster till EU-företag under perioden?
→ Om ja, periodisk sammanställning obligatorisk
□ Har du kundens korrekta VAT-nummer?
→ Verifiera på VIES: https://ec.europa.eu/taxation_customs/vies/
□ Har du lämnat sammanställningen i tid?
→ Deadline: 20:e i månaden efter kvartalets slut
→ Q1: 20 april
→ Q2: 20 juli
→ Q3: 20 oktober
→ Q4: 20 januari
□ Stämmer beloppen med Ruta 39?
→ Summorna MÅSTE matcha exakt
```
**Åtgärder vid miss:**
1. Lämna in försenad sammanställning omedelbart
2. Betala förseningsavgift (1,250 kr)
3. Korrigera momsdeklaration om nödvändigt
---
### 1.3 Osäker på vilken momsregel som gäller
**Frågor att ställa:**
```
1. Vem är kunden?
□ Företag → Var är de registrerade?
□ Privatperson → Var bor de?
2. Är det en vara eller tjänst?
□ Vara → Leveransplats avgör
□ Tjänst → Kundens säte avgör (B2B)
3. Har kunden VAT-nummer?
□ Ja → Verifiera det
□ Nej → Behandla som privatperson
```
---
## 2. Bokföringsproblem
### 2.1 Blandade privata och företagstransaktioner
**Symptom:** Oklara verifikationer, sammanblandade konton.
**Kontrollpunkter:**
```
□ Har du separata bankkonton?
→ Privat
→ EF (om tillämpligt)
→ AB (om tillämpligt)
□ Har privata inköp betalats från företagskontot?
→ Bokför som eget uttag (EF) eller lån till ägare (AB)
□ Har företagsinköp betalats privat?
→ Bokför som insättning eget kapital (EF) eller lån från ägare (AB)
```
**Åtgärder:**
1. Gå igenom alla kontoutdrag
2. Markera alla transaktioner som företag/privat
3. Bokför korrigeringar för felaktiga
4. Upprätta rutin för framtiden
---
### 2.2 Saknar verifikationer
**Symptom:** Transaktioner utan underlag.
**Kontrollpunkter:**
```
□ Vilken typ av transaktion?
→ Plattformsintäkt: Ladda ner från plattformen
→ Inköp: Kontakta leverantör för kopia
→ Barter: Upprätta eget underlag
□ Kan du rekonstruera underlaget?
→ Screenshot av beställning
→ E-postbekräftelse
→ Bankutdrag som stöd
```
**Barter-underlag som du själv upprättar:**
```
VERIFIKATION - MOTTAGEN PRODUKT
Datum: [mottagningsdatum]
Produkt: [beskrivning]
Avsändare: [företag/person]
Marknadsvärde inkl. moms: [belopp] kr
Källa för värdering: [prislapp/webbshop/etc.]
Motprestation: [beskrivning av inlägg/samarbete]
Undertecknad av: [influencerns namn]
```
---
### 2.3 Valutaomräkning stämmer inte
**Symptom:** Differenser mellan faktura och betalning.
**Kontrollpunkter:**
```
□ Vilken kurs använde du vid fakturering?
→ Ska vara kurs vid fakturadatum
□ Vilken kurs gällde vid betalning?
→ Kontrollera Riksbankens officiella kurs
□ Har du bokfört kursdifferensen separat?
→ Vinst: Konto 3960
→ Förlust: Konto 7960
```
**Beräkningsexempel:**
```
Faktura: €1,000 @ 11.50 = 11,500 kr
Betalning: €1,000 @ 11.35 = 11,350 kr
Kursförlust: 150 kr
Bokföring:
Debet 1940 Bank: 11,350
Debet 7960 Valutakursförluster: 150
Kredit 1510 Kundfordringar: 11,500
```
---
## 3. Skattedeklarationsproblem
### 3.1 Saknar F-skatt men har fakturerat
**Symptom:** Uppdragsgivare har inte dragit skatt, eller faktura utställd utan F-skatt.
**Kontrollpunkter:**
```
□ Hade du F-skatt när fakturan ställdes ut?
→ Om nej: Uppdragsgivaren ska ha dragit A-skatt
□ Drog uppdragsgivaren skatt?
→ Kontrollera lönespecifikation/kontrolluppgift
□ Betalade uppdragsgivaren arbetsgivaravgifter?
→ Ska synas på kontrolluppgift
```
**Åtgärder:**
1. Om skatt EJ drogs: Deklarera inkomsten, du ansvarar för skatten
2. Kontakta uppdragsgivaren för kontrolluppgift
3. Ansök om F-skatt om du planerar fortsätta
---
### 3.2 Fått preliminärskattebesked med fel belopp
**Symptom:** Debiterad F-skatt stämmer inte med förväntad inkomst.
**Kontrollpunkter:**
```
□ Baseras beskedet på gammal/felaktig uppgift?
→ Skatteverket använder senaste deklarationen
□ Har din verksamhet förändrats väsentligt?
→ Ökade/minskade intäkter
□ Har du redovisat alla intäkter korrekt?
```
**Åtgärder:**
1. Logga in på Skatteverket
2. Ändra preliminär inkomstdeklaration
3. Ny debitering beräknas automatiskt
---
### 3.3 Kvarskatt trots F-skatt
**Symptom:** Slutskattebesked visar kvarskatt.
**Vanliga orsaker:**
```
□ Debiterad F-skatt var för låg
→ Inkomsten blev högre än beräknat
□ Egenavgifter ej medräknade korrekt
→ Särskilt relevant för EF
□ Intäkter utan avdragen skatt
→ Utländska plattformar drar ej svensk skatt
→ Barter-produkter har ej skatt avdragen
□ Kapitalvinster/utdelningar
→ Beskattas separat
```
**Åtgärder:**
1. Analysera slutskattebeskedet
2. Justera kommande års preliminärskatt
3. Skapa buffert för kvarskatt
---
## 4. SGI-problem
### 4.1 SGI är 0 eller mycket låg
**Symptom:** Försäkringskassan meddelar låg SGI.
**Kontrollpunkter för EF:**
```
□ Vad visar NE-bilagan som överskott?
→ SGI = överskott
□ Har du gjort stora avdrag?
→ Sänker överskottet och därmed SGI
□ Är verksamheten i uppbyggnadsskede (<36 mån)?
→ Kan få jämförelseinkomst istället
```
**Kontrollpunkter för AB:**
```
□ Hur mycket lön har du tagit ut?
→ SGI = utbetald lön
□ Tar du mest utdelning?
→ Utdelning räknas EJ i SGI
□ Är lönen "marknadsmässig"?
→ För låg lön kan ifrågasättas
```
**Åtgärder:**
1. Beräkna vilken SGI du behöver
2. Justera lön/överskott för att nå nivån
3. Kontakta Försäkringskassan för omprövning
---
### 4.2 Väntar barn men har låg SGI
**Symptom:** Oro för låg föräldrapenning.
**Tidslinje:**
```
SGI fastställs vid:
- Födsel (för föräldrapenning)
- Baseras på inkomst ~12 månader bakåt
Åtgärd om <12 månader kvar:
→ Höj lön/överskott OMEDELBART
→ Varje månad med högre inkomst hjälper
Åtgärd om >12 månader kvar:
→ Planera inkomstfördelning noggrant
→ Sikta på SGI ≥599,250 kr för maximal föräldrapenning
```
---
## 5. Plattformsproblem
### 5.1 Hittar inte fakturaunderlag
**Per plattform:**
| Plattform | Var hittar jag underlag? |
|-----------|--------------------------|
| YouTube | YouTube Studio → Analytics → Revenue → Transaction history i AdSense |
| Twitch | Creator Dashboard → Channel Analytics → Revenue |
| Instagram | Professional Dashboard → Monetization → Payouts |
| TikTok | TikTok Creator Portal → Balance → Transaction History |
| Patreon | Creator Dashboard → Income → Payout History |
**Om underlag saknas:**
1. Kontakta plattformens support
2. Använd bankutdrag som stöd
3. Upprätta egen verifikation med tillgänglig information
---
### 5.2 Plattform har hållit inne skatt (W-8BEN)
**Symptom:** 30% avdrag på USA-baserade plattformar.
**Kontrollpunkter:**
```
□ Har du fyllt i W-8BEN korrekt?
→ Formulär som intygar icke-amerikansk skattestatus
□ Har formuläret löpt ut?
→ Giltigt i 3 år
→ Måste förnyas
□ Har du angett korrekt land?
→ Sverige har skatteavtal med USA
```
**Åtgärder:**
1. Logga in på plattformen
2. Gå till skatteuppgifter/tax information
3. Fyll i nytt W-8BEN
4. Kontakta support om innehållen skatt kan återbetalas
---
## 6. Juridiska problem
### 6.1 Klagomål om reklammarkering
**Symptom:** Varning från Konsumentverket eller klagomål.
**Omedelbara åtgärder:**
```
1. Redigera inlägget
→ Lägg till "REKLAM" överst
→ Se till att det är tydligt synligt
2. Dokumentera
→ Ta screenshot av korrigerat inlägg
→ Notera datum och tid för ändring
3. Informera uppdragsgivaren
→ De kan också bli ansvariga
```
**Förebyggande:**
- Märk ALLA betalda samarbeten
- Placera märkning ÖVERST
- Vid osäkerhet: Märk ändå
---
### 6.2 Upphovsrättskrav (musik, bilder)
**Symptom:** Copyright claim, krav på ersättning, nedtagning.
**Omedelbara åtgärder:**
```
1. Ta ner innehållet
→ Stoppar ytterligare intrång
2. Svara INTE på krav utan juridisk rådgivning
→ Medge ingenting
3. Dokumentera
→ Spara allt material
→ Notera publiceringsperiod
```
**Förebyggande:**
- Använd endast licensierad musik
- Epidemic Sound, Artlist = säkra alternativ
- Kontrollera bildlicenser
---
## 7. Dubbel struktur-problem
### 7.1 Blandat EF och AB
**Symptom:** Oklart vilket företag som fakturerat/betalat vad.
**Kontrollpunkter:**
```
□ Finns transaktioner bokförda i fel företag?
→ Gå igenom alla verifikationer
□ Har samma kostnad bokförts i båda?
→ Dubbelräkning ej tillåten
□ Har internfakturor hanterats korrekt?
→ Moms ska vara med
→ Avtal ska finnas
```
**Åtgärder:**
1. Gör fullständig genomgång av båda bokföringarna
2. Upprätta lista över alla internfakturor
3. Korrigera felaktiga bokningar
4. Upprätta tydliga rutiner
---
### 7.2 Skatteverket ifrågasätter internprissättning
**Symptom:** Frågor om transaktioner mellan EF och AB.
**Dokumentation som behövs:**
```
□ Skriftliga avtal för alla transaktioner
□ Prissättningsunderlag (marknadsjämförelser)
□ Affärsmässig motivering
□ Faktiska leveranser/prestationer
```
**Försvar:**
1. Visa att priset är marknadsmässigt
2. Visa att verklig tjänst/vara levererades
3. Visa affärsmässigt syfte (ej endast skatteplanering)
---
## 8. Akuta situationer
### 8.1 Missad deadline
| Deadline | Konsekvens vid miss | Åtgärd |
|----------|---------------------|--------|
| Momsdeklaration | Förseningsavgift | Lämna in omedelbart |
| Periodisk sammanställning | 1,250 kr avgift | Lämna in omedelbart |
| Inkomstdeklaration | Förseningsavgift + skönstaxering | Kontakta Skatteverket |
| F-skatt betalning | Ränta | Betala + kontakta Skatteverket |
### 8.2 Kontrollbesked från Skatteverket
**Steg:**
1. Läs noggrant vad de frågar om
2. Samla all relevant dokumentation
3. Svara inom angiven tid
4. Vid osäkerhet: Anlita rådgivare
5. Var sanningsenlig men ange inte mer än vad som frågas
### 8.3 Revision
**Förberedelser:**
1. Samla ALL bokföring för perioden
2. Kontrollera att verifikationer finns
3. Förbered förklaringar för ovanliga poster
4. Anlita redovisningskonsult om möjligt
@@ -0,0 +1,306 @@
# Kalender och deadlines
## Årsöversikt
### Januari
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) december | Månadsredovisare |
| 12 | Arbetsgivardeklaration december | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 20 | Periodisk sammanställning Q4 | EU-försäljning |
| 26 | Momsdeklaration Q4 (okt-dec) | Kvartalsredovisare |
| 31 | Kontrolluppgifter till Skatteverket | AB (löner) |
### Februari
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) januari | Månadsredovisare |
| 12 | Arbetsgivardeklaration januari | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 28 | Preliminär inkomstdeklaration (ändring) | Om behov finns |
### Mars
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) februari | Månadsredovisare |
| 12 | Arbetsgivardeklaration februari | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 31 | Bokslut räkenskapsår (kalenderår) | AB |
### April
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) mars | Månadsredovisare |
| 12 | Arbetsgivardeklaration mars | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 20 | Periodisk sammanställning Q1 | EU-försäljning |
| 26 | Momsdeklaration Q1 (jan-mar) | Kvartalsredovisare |
### Maj
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 2 | Inkomstdeklaration privatperson + NE | EF |
| 12 | Momsdeklaration (månads) april | Månadsredovisare |
| 12 | Arbetsgivardeklaration april | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
### Juni
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) maj | Månadsredovisare |
| 12 | Arbetsgivardeklaration maj | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 30 | Årsredovisning till Bolagsverket | AB (kalenderår) |
### Juli
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 1 | Inkomstdeklaration AB (kalenderår) | AB |
| 12 | Momsdeklaration (månads) juni | Månadsredovisare |
| 12 | Arbetsgivardeklaration juni | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 20 | Periodisk sammanställning Q2 | EU-försäljning |
| 26 | Momsdeklaration Q2 (apr-jun) | Kvartalsredovisare |
### Augusti
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) juli | Månadsredovisare |
| 12 | Arbetsgivardeklaration juli | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| ~15 | Slutskattebesked | Alla |
### September
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) augusti | Månadsredovisare |
| 12 | Arbetsgivardeklaration augusti | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| ~12 | Kvarskatt inbetalning 1 | Vid kvarskatt |
### Oktober
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) september | Månadsredovisare |
| 12 | Arbetsgivardeklaration september | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 20 | Periodisk sammanställning Q3 | EU-försäljning |
| 26 | Momsdeklaration Q3 (jul-sep) | Kvartalsredovisare |
### November
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) oktober | Månadsredovisare |
| 12 | Arbetsgivardeklaration oktober | AB med anställda |
| ~12 | Kvarskatt inbetalning 2 | Vid kvarskatt |
| 17 | F-skatt inbetalning | EF och AB |
### December
| Dag | Händelse | Gäller |
|-----|----------|--------|
| 12 | Momsdeklaration (månads) november | Månadsredovisare |
| 12 | Arbetsgivardeklaration november | AB med anställda |
| 17 | F-skatt inbetalning | EF och AB |
| 27 | Sista arbetsdag före jul (myndigheter) | Planering |
| 31 | Räkenskapsårets slut | Kalenderår |
---
## Kvartalsvisa deadlines
### Momsdeklaration (kvartalsredovisare)
| Kvartal | Period | Deadline |
|---------|--------|----------|
| Q1 | Jan-Mar | 12 maj (papper) / 26 april (e-tjänst) |
| Q2 | Apr-Jun | 17 aug (papper) / 26 juli (e-tjänst) |
| Q3 | Jul-Sep | 12 nov (papper) / 26 okt (e-tjänst) |
| Q4 | Okt-Dec | 12 feb (papper) / 26 jan (e-tjänst) |
### Periodisk sammanställning (EU-försäljning)
| Kvartal | Period | Deadline |
|---------|--------|----------|
| Q1 | Jan-Mar | 20 april |
| Q2 | Apr-Jun | 20 juli |
| Q3 | Jul-Sep | 20 oktober |
| Q4 | Okt-Dec | 20 januari |
### OSS-deklaration (EU-privatpersonsförsäljning)
| Kvartal | Period | Deadline |
|---------|--------|----------|
| Q1 | Jan-Mar | 30 april |
| Q2 | Apr-Jun | 31 juli |
| Q3 | Jul-Sep | 31 oktober |
| Q4 | Okt-Dec | 31 januari |
---
## F-skatt inbetalningsdatum
| Månad | Inbetalningsdatum |
|-------|-------------------|
| Januari | 17 januari |
| Februari | 17 februari |
| Mars | 17 mars |
| April | 17 april |
| Maj | 17 maj |
| Juni | 17 juni |
| Juli | 17 juli |
| Augusti | 17 augusti |
| September | 17 september |
| Oktober | 17 oktober |
| November | 17 november |
| December | 17 december |
**OBS:** Om den 17:e infaller på helg, gäller närmast följande vardag.
---
## Löpande uppgifter
### Dagligen/Veckovis
- [ ] Spara kvitton och verifikationer
- [ ] Notera mottagna produkter (barter)
- [ ] Dokumentera arbetsresor
### Månadsvis
- [ ] Stäm av bankkonton mot bokföring
- [ ] Bokför månadens transaktioner
- [ ] Kontrollera plattformsutbetalningar
- [ ] Beräkna eventuella valutakursdifferenser
- [ ] Momsdeklaration (om månadsredovisare)
### Kvartalsvis
- [ ] Momsdeklaration (om kvartalsredovisare)
- [ ] Periodisk sammanställning (om EU-försäljning)
- [ ] OSS-deklaration (om EU-privatpersonsförsäljning)
- [ ] Resultatuppföljning
- [ ] SGI-kontroll
### Årligen
**Januari-februari:**
- [ ] Upprätta årsbokslut för föregående år
- [ ] Sammanställ alla intäkter och kostnader
- [ ] Beräkna avskrivningar
- [ ] Hantera periodiseringsfonder (EF)
**Mars-april:**
- [ ] Förbered inkomstdeklaration
- [ ] Kontrollera kontrolluppgifter
- [ ] Granska förifylld deklaration
**Maj:**
- [ ] Lämna inkomstdeklaration (2 maj)
- [ ] NE-bilaga (EF)
**Juni-juli:**
- [ ] Årsredovisning till Bolagsverket (AB)
- [ ] Inkomstdeklaration AB (1 juli)
**Augusti-september:**
- [ ] Analysera slutskattebesked
- [ ] Justera preliminärskatt för nästa år
- [ ] Planera för eventuell kvarskatt
**Oktober-december:**
- [ ] Skatteplanering inför årsskiftet
- [ ] 3:12-beräkning (AB)
- [ ] Pensionssparande (avdragsgillt)
- [ ] Friskvårdsbidrag (AB)
---
## Påminnelser per bolagsform
### Enskild firma (EF)
| Tidpunkt | Uppgift |
|----------|---------|
| Månadsvis | Bokföring, avstämning |
| Kvartalsvis | Moms, periodisk sammanställning |
| 2 maj | Inkomstdeklaration + NE-bilaga |
| Löpande | Dokumentera barter, resor, avdrag |
### Aktiebolag (AB)
| Tidpunkt | Uppgift |
|----------|---------|
| Månadsvis | Bokföring, lönehantering, arbetsgivardeklaration |
| Kvartalsvis | Moms, periodisk sammanställning |
| 31 mars | Bokslut (kalenderår) |
| 30 juni | Årsredovisning till Bolagsverket |
| 1 juli | Inkomstdeklaration AB |
| December | 3:12-beräkning, utdelningsbeslut |
### Dubbel struktur (EF + AB)
| Tidpunkt | Uppgift |
|----------|---------|
| Månadsvis | Separat bokföring båda, internfakturor |
| Kvartalsvis | Separata momsdeklarationer, periodiska sammanställningar |
| 2 maj | Inkomstdeklaration privatperson + NE |
| 30 juni | Årsredovisning AB |
| 1 juli | Inkomstdeklaration AB |
| Löpande | Avstämning interntransaktioner |
---
## Kritiska datum att INTE missa
### Förseningsavgifter
| Uppgift | Avgift vid miss |
|---------|-----------------|
| Periodisk sammanställning | 1,250 kr |
| Momsdeklaration | 625 kr (ökar vid upprepad försening) |
| Inkomstdeklaration | 1,250 kr (kan öka) |
| Årsredovisning | Förseningsavgift + risk för likvidation |
### Viktiga gränsdatum
| Datum | Händelse |
|-------|----------|
| 2 maj | Sista dag inkomstdeklaration privatperson |
| 30 juni | Sista dag årsredovisning AB (6 mån efter bokslut) |
| 1 juli | Sista dag inkomstdeklaration AB (kalenderår) |
| 31 december | Räkenskapsårets slut, sista dag för avdragsgilla pensionsinbetalningar |
---
## Platshållare för användarspecifika påminnelser
### Min bolagsform: [EF / AB / Båda]
### Mina deadlines:
| Uppgift | Deadline | Status |
|---------|----------|--------|
| Momsdeklaration Q_ | | |
| Periodisk sammanställning Q_ | | |
| F-skatt månad | | |
| Inkomstdeklaration | | |
| Årsredovisning | | |
### Mina återkommande uppgifter:
- [ ] ...
- [ ] ...
@@ -0,0 +1,542 @@
# Varningsflaggor
## 1. Bokföringsvarningar
### 1.1 Sammanblandning av entiteter
**Flagga:** Transaktion verkar bokförd i fel företag
**Triggers:**
- Samma fakturanummer i både EF och AB
- Kostnad bokförd i AB men betald från EF-konto
- Intäkt från plattform bokförd i båda entiteterna
**AI-åtgärd:**
```
VARNING: Denna transaktion kan vara bokförd i fel entitet.
Kontrollera:
- Vilken entitet har avtalet med kunden?
- Från vilket konto gjordes betalningen?
- Har samma transaktion bokförts i den andra entiteten?
```
---
### 1.2 Privata kostnader i verksamheten
**Flagga:** Misstänkt privat kostnad bokförd som företagskostnad
**Triggers:**
- Klädinköp (ej uniformer/rekvisita)
- Smink och skönhetsprodukter
- Gym/träningskort (i EF)
- Restaurangbesök utan dokumenterad affärsanledning
- Resor utan arbetssyfte
**AI-åtgärd:**
```
VARNING: Denna kostnad kan vara privat och ej avdragsgill.
[Typ av kostnad] kräver:
- Dokumentation av affärssyfte
- Kan endast dras av om [specifika villkor]
Vill du fortsätta bokföra detta som företagskostnad?
```
---
### 1.3 Saknad verifikation
**Flagga:** Transaktion utan tillräckligt underlag
**Triggers:**
- Inbetalning utan faktura/avtal
- Barter-produkt utan värderingsunderlag
- Resa utan syftesdokumentation
**AI-åtgärd:**
```
VARNING: Verifikation saknas eller är ofullständig.
För att bokföra behövs:
- [Lista på kravd dokumentation]
Vill du skapa ett eget underlag?
```
---
### 1.4 Orimliga belopp
**Flagga:** Belopp avviker kraftigt från normalt
**Triggers:**
- Enskilt inköp >50,000 kr utan förklaring
- Negativ intäkt (rättelse?)
- Valutakurs utanför rimligt intervall
**AI-åtgärd:**
```
VARNING: Beloppet verkar ovanligt.
- Bokfört belopp: [X] kr
- Normalt intervall: [Y-Z] kr
Kontrollera att beloppet är korrekt.
```
---
## 2. Momsvarningar
### 2.1 Periodisk sammanställning saknas
**Flagga:** EU-försäljning utan periodisk sammanställning
**Triggers:**
- Försäljning bokförd i Ruta 39
- Ingen periodisk sammanställning inlämnad för perioden
- Periodisk sammanställning ≠ Ruta 39
**AI-åtgärd:**
```
VARNING: Periodisk sammanställning krävs.
Du har bokfört [X] kr i EU-försäljning under [kvartal].
Deadline för periodisk sammanställning: [datum]
Förseningsavgift vid miss: 1,250 kr
Kunder att rapportera:
- [Företag 1]: [VAT-nummer], [belopp]
- [Företag 2]: [VAT-nummer], [belopp]
```
---
### 2.2 Felaktig momshantering
**Flagga:** Moms hanterad inkonsekvent med kundtyp
**Triggers:**
- Svensk moms debiterad till EU-företag med VAT-nummer
- Ingen moms debiterad till svensk kund
- Export-moms på EU-försäljning
**AI-åtgärd:**
```
VARNING: Momshanteringen verkar felaktig.
Kund: [Kundnamn]
Kundens säte: [Land]
VAT-nummer: [Om finns]
Korrekt momshantering:
- [Regel som borde gälla]
- Ruta i momsdeklaration: [X]
Vill du korrigera?
```
---
### 2.3 OSS-tröskel överskriden
**Flagga:** EU-privatpersonförsäljning överstiger 99,680 kr
**Triggers:**
- Summa B2C EU-försäljning >99,680 kr under året
- Fortsatt svensk moms efter överskridande
**AI-åtgärd:**
```
VARNING: OSS-tröskel överskriden.
Total EU-privatpersonsförsäljning: [X] kr
Tröskel: 99,680 kr
Konsekvens:
- Obligatoriskt att debitera mottagarlandets moms
- Registrera för OSS hos Skatteverket
- Lämna kvartalsvis OSS-deklaration
Tidigare försäljning under året kan behöva korrigeras.
```
---
### 2.4 Ogiltigt VAT-nummer
**Flagga:** VAT-nummer kunde inte verifieras
**Triggers:**
- VAT-nummer angivet men validering misslyckades
- Format stämmer ej med landskod
**AI-åtgärd:**
```
VARNING: VAT-nummer kunde inte verifieras.
Angivet nummer: [VAT-nummer]
Land: [Landkod]
Verifiera på VIES: https://ec.europa.eu/taxation_customs/vies/
Om ogiltigt:
- Kunden behandlas som privatperson
- Svensk moms eller OSS gäller
```
---
## 3. SGI-varningar
### 3.1 Låg SGI-grund
**Flagga:** SGI-grundande inkomst under rekommenderad nivå
**Triggers:**
- EF: Överskott <200,000 kr/år
- AB: Lön <200,000 kr/år
- Total SGI-grund <400,000 kr/år
**AI-åtgärd:**
```
VARNING: Lågt socialförsäkringsskydd.
Din SGI-grund: [X] kr/år
Rekommenderad miniminivå: 400,000 kr/år
Vid sjukdom:
- Nuvarande sjukpenning: ~[Y] kr/dag
- Med rekommenderad SGI: ~[Z] kr/dag
Vid föräldraledighet (390 dagar):
- Nu: [A] kr totalt
- Rekommenderat: [B] kr totalt
- Skillnad: [C] kr
Överväg att:
- EF: Minska avdrag / öka överskott
- AB: Höja löneuttag
```
---
### 3.2 Hög utdelning, låg lön (AB)
**Flagga:** Obalans mellan lön och utdelning
**Triggers:**
- Utdelning >3x lön
- Lön under 300,000 kr med vinst >500,000 kr
- SGI-konsekvens ej beaktad
**AI-åtgärd:**
```
VARNING: Skatteoptimering påverkar ditt sociala skydd.
Uttagen lön: [X] kr
Planerad utdelning: [Y] kr
Din SGI: [X] kr (endast lön räknas)
Konsekvens:
- Sjukpenning baseras på [X] kr
- Föräldrapenning baseras på [X] kr
- Utdelning [Y] kr ger INGET socialt skydd
Rekommendation: Balansera lön och utdelning utifrån ditt behov av trygghet.
```
---
### 3.3 Planerad föräldraledighet
**Flagga:** Användare nämner barn/föräldraledighet med låg SGI
**Triggers:**
- Nyckelord: "gravid", "barn", "föräldraledigt", "VAB"
- Kombinerat med låg SGI-grund
**AI-åtgärd:**
```
VIKTIGT: Föräldrapenning baseras på din SGI.
Nuvarande SGI-grund: [X] kr
Om barn planeras inom 12 månader:
- SGI fastställs vid barnets födelse
- Varje månad med högre inkomst höjer SGI
Åtgärd NU:
- EF: Redovisa högre överskott
- AB: Höj månadslönen
- Sikta på SGI ≥599,250 kr för maximal föräldrapenning
```
---
## 4. Strukturvarningar (dubbel struktur)
### 4.1 Internprissättning avviker
**Flagga:** Pris mellan EF och AB verkar icke-marknadsmässigt
**Triggers:**
- Timpris <300 kr eller >2,000 kr
- Avvikelse >20% från jämförbar extern prissättning
- Pris utan dokumenterad grund
**AI-åtgärd:**
```
VARNING: Internprissättningen kan ifrågasättas.
Transaktion: [Beskrivning]
Pris: [X] kr
Marknadsmässigt intervall: [Y-Z] kr
Skatteverket kräver armlängdspris.
Vid avvikelse:
- Risk för omklassificering
- Förtäckt lön/utdelning beskattas hårdare
Rekommendation: Dokumentera varför priset är marknadsmässigt.
```
---
### 4.2 Avtal saknas för interntransaktion
**Flagga:** Faktura mellan EF och AB utan avtal
**Triggers:**
- Internfaktura bokförd
- Inget avtal registrerat i systemet
**AI-åtgärd:**
```
VARNING: Avtal saknas för interntransaktion.
Transaktion: [Beskrivning]
Belopp: [X] kr
Krav för godkänd interntransaktion:
1. Skriftligt avtal
2. Marknadsmässig prissättning
3. Verklig tjänst/vara levererad
4. Affärsmässig motivering
Upprätta avtal FÖRE fakturering.
```
---
### 4.3 Alla kostnader i ett företag, alla intäkter i annat
**Flagga:** Misstänkt resultatförflyttning
**Triggers:**
- EF har endast kostnader (förlust)
- AB har endast intäkter (hög vinst)
- Eller vice versa
**AI-åtgärd:**
```
VARNING: Obalanserad resultatfördelning.
EF resultat: [X] kr
AB resultat: [Y] kr
Detta mönster kan tyda på:
- Felaktig allokering av kostnader
- Resultatförflyttning (ej tillåtet utan affärsmässig grund)
Kontrollera:
- Är kostnaderna bokförda i rätt entitet?
- Har rätt entitet fakturerat kunderna?
- Finns underlag för fördelningen?
```
---
## 5. Juridiska varningar
### 5.1 Reklammarkering saknas
**Flagga:** Betalt samarbete utan tydlig reklammarkering
**Triggers:**
- Inkomst från varumärke/plattform
- Ingen notering om reklammarkering
- Samarbetsavtal registrerat
**AI-åtgärd:**
```
PÅMINNELSE: Kontrollera reklammarkering.
Samarbete med: [Företag]
Ersättning: [X] kr
Krav enligt Marknadsföringslagen:
- "REKLAM" eller "ANNONS" överst i inlägg
- Tydlig och omedelbart synlig
- I video: Muntligt OCH/ELLER textgrafik
Underlåtenhet kan leda till:
- Varning från Konsumentverket
- Förbud vid vite
- Marknadsstörningsavgift
```
---
### 5.2 Upphovsrättskänsligt material
**Flagga:** Risk för upphovsrättsintrång
**Triggers:**
- Video/musik-content i kommersiellt sammanhang
- Ingen licensdokumentation
- Känd låt/artist nämnd
**AI-åtgärd:**
```
VARNING: Kontrollera upphovsrätt.
Om du använder musik/bilder/video:
- Har du licens?
- Är materialet royalty-free?
Utan licens:
- Risk för skadestånd (referens: ABBA-fallet 200,000 kr)
- Innehåll kan tas ner
- Plattformskonto kan stängas
Säkra alternativ:
- Epidemic Sound
- Artlist
- Creative Commons
```
---
## 6. Administrativa varningar
### 6.1 Deadline närmar sig
**Flagga:** Viktig deadline inom 7 dagar
**Triggers:**
- Momsdeklaration oinlämnad
- Periodisk sammanställning oinlämnad
- F-skatt obetald
- Inkomstdeklaration oinlämnad
**AI-åtgärd:**
```
DEADLINE-VARNING: [Uppgift]
Deadline: [Datum] ([X] dagar kvar)
Status: [Ej påbörjad / Påbörjad / Klar]
Förseningsavgift vid miss: [Belopp] kr
[Länk/instruktion för att slutföra]
```
---
### 6.2 Saknad kontrolluppgift
**Flagga:** Inkomst utan motsvarande kontrolluppgift
**Triggers:**
- Intäkt bokförd från svenskt företag
- Ingen kontrolluppgift i förväg
- Deklarationsperiod närmar sig
**AI-åtgärd:**
```
VARNING: Kontrolluppgift kan saknas.
Inkomst från: [Företag]
Belopp: [X] kr
Period: [År/Månad]
Kontrollera:
- Fick du kontrolluppgift?
- Om nej, kontakta [Företag]
Kontrolluppgifter ska finnas i e-tjänsten senast [datum].
```
---
### 6.3 Valutakurs ej dokumenterad
**Flagga:** Utländsk transaktion utan kursdokumentation
**Triggers:**
- Belopp i USD/EUR/annan valuta
- Ingen valutakurs angiven
- Stor skillnad mellan möjliga kurser
**AI-åtgärd:**
```
VARNING: Valutakurs behöver dokumenteras.
Transaktion: [Beskrivning]
Belopp: [Valuta] [Originalbelopp]
Datum: [Inbetalningsdatum]
Ange valutakurs eller hämta automatiskt:
- Riksbankens kurs [datum]: [X.XX]
- Beräknat SEK-belopp: [Y] kr
Kursen ska vara den som gällde vid inbetalningsdatum.
```
---
## 7. Automatiska kontroller
### 7.1 Periodiska avstämningar
```
MÅNATLIG KONTROLL:
□ Summa intäkter plattformar = Summa bokförda intäkter?
□ Banksaldo = Bokfört saldo?
□ Ingående moms rimlig i förhållande till kostnader?
□ Utgående moms rimlig i förhållande till intäkter?
KVARTALSVIS KONTROLL:
□ Periodisk sammanställning = Ruta 39?
□ SGI-grund tillräcklig?
□ Internfakturor avstämda mellan EF och AB?
ÅRSVIS KONTROLL:
□ Alla kontrolluppgifter mottagna?
□ Preliminärskatt vs faktisk skatt?
□ Periodiseringsfonder inom gränser?
□ 3:12-gränsbelopp beräknat?
```
---
### 7.2 Flaggningströsklar
| Kontroll | Flagga om |
|----------|-----------|
| SGI-grund | <200,000 kr/år |
| Lön vs utdelning (AB) | Utdelning >3x lön |
| Internpris | Avviker >20% från marknad |
| Periodisk sammanställning | Saknas vid EU-försäljning |
| Momsavvikelse | >5% differens |
| Kostnad utan verifikation | Alltid |
| Privat kostnad i företag | Alltid |
| Deadline | <7 dagar kvar |
---
## 8. Eskaleringsregler
### När ska AI rekommendera extern rådgivning?
```
REKOMMENDERA REVISOR/REDOVISNINGSKONSULT:
- Omsättning >3 MSEK/år
- Komplex dubbel struktur
- Internationella transaktioner med osäker momshantering
- Skatteverket har skickat förfrågan
REKOMMENDERA JURIST:
- Upphovsrättskrav
- Avtalsförhandling med stort värde (>100,000 kr)
- Tvist med uppdragsgivare
- Marknadsföringsrättslig varning
REKOMMENDERA FÖRSÄKRINGSKASSAN:
- SGI-relaterade frågor
- Uppbyggnadsskede-bedömning
- Omprövning av beslut
```