<aside> 💡 Ein AgentSkills-kompatibler Skill für die automatische Erstellung von Bexio-Offerten via API. mit OpenClaw. Ist aber auch für andere agentische Systeme wie Manus anpassbar.

</aside>

Was macht dieser Skill?


Setup

  1. API Token erstellen auf developer.bexio.com
  2. Token speichern (Anleitung im Skill, Zeile 8–9)
  3. Skill-Ordner in dein AgentSkills-Verzeichnis kopieren
  4. Eigene IDs einmalig ermitteln: python3 bexio.py --get-ids

<aside> ⚠️ Wichtig: mwst_type: "included" + mwst_is_net: true ist die einzige korrekte Kombination für Schweizer MwSt-Berechnung in Bexio.

</aside>


SKILL.md

---
name: bexio-offerten
description: >
  Erstellt Offerten in Bexio via API. Umfasst Kontakt suchen oder neu anlegen,
  Offerte mit Positionen erstellen, als offen markieren und PDF-Link abrufen.
  Unterstuetzt einfache Custom-Positionen sowie Gruppen mit Unterpositionen,
  optionale Positionen und Rabatte. Triggerphrasen: Offerte erstellen,
  Angebot erstellen, Bexio Offerte, Offerte fuer Firmenname, neues Angebot.
---

# Bexio Offerten

## Setup

1. API Token erstellen auf <https://developer.bexio.com>
2. Token speichern: `read -rs KEY && echo "$KEY" > ~/.credentials/bexio-api-key && chmod 600 ~/.credentials/bexio-api-key`
3. Eigene IDs einmalig ermitteln (siehe references/api.md → IDs ermitteln)

## Workflow

1. **Kontakt klären** → suchen, bestätigen oder neu anlegen
2. **Offerte zusammenstellen** → Blueprint wählen, Positionen anpassen
3. **Offerte erstellen** via Script
4. **Als offen markieren** wenn bereit zum Versenden

Lese `references/api.md` für API-Endpunkte, Feld-IDs und Fallstricke.
Lese `references/blueprints.md` für Positions-Vorlagen.

---

## Phase 1: Kontakt

**Immer zuerst suchen.**

```bash
python3 scripts/bexio.py --search-contact "Firmenname"

Treffer gefunden → vorlegen und bestätigen lassen: «Gefunden: [ID] [Firma] [Ort] [Mail] — ist das der richtige Kontakt?» Erst nach Bestätigung weitermachen.

Kein Treffer → fehlende Angaben erfragen, dann neu anlegen:

POST /contact
{
  "contact_type_id": 2,
  "name_1": "Firma AG",
  "name_2": "Max Muster",
  "address": "Strasse 1",
  "postcode": "8000",
  "city": "Stadt",
  "country_id": 1,
  "mail": "[email protected]",
  "language_id": 1
}

Phase 2: Offerte

POST /3.0/kb_offer
{
  "title": "Leistung – Firma AG",
  "contact_id": 123,
  "user_id": 1,
  "language_id": 1,
  "bank_account_id": 1,
  "currency_id": 1,
  "logopaper_id": 1,
  "mwst_type": "included",
  "mwst_is_net": true,
  "is_valid_from": "YYYY-MM-DD",
  "is_valid_until": "YYYY-MM-DD",
  "header": "Liebe

references/api.md

# Bexio API – Referenz

## Setup
- **API Token:** <https://developer.bexio.com> (nicht über office.bexio.com)
- **Base URL:** `https://api.bexio.com/3.0`
- **Headers:** `Authorization: Bearer {token}`, `Accept: application/json`, `Content-Type: application/json`

## IDs ermitteln (einmalig)

```bash
# Eigene User-ID
GET /users

# Bank-Accounts
GET /bank_accounts

# Steuersätze (z.B. MwSt 8.1%)
GET /taxes

# Einheiten (z.B. Pauschal, Tag, Stunde)
GET /units

# Konten (Erlöse)
GET /accounts

Wichtige Standard-IDs (Schweiz)

Feld Wert
country_id Schweiz 1
language_id Deutsch 1
currency_id CHF 1
contact_type Firma 2
contact_type Person 1

Positions-Typen

custom (Leistungsposition mit Preis)

{
  "type": "custom",
  "pos": "1",
  "is_optional": false,
  "amount": 1,
  "unit_id": DEINE_EINHEIT_ID,
  "tax_id": DEIN_STEUERSATZ_ID,
  "title": null,
  "text": "<strong>Titel</strong><br /><ul><li>Punkt 1</li></ul>",
  "unit_price": 1000,
  "discount_in_percent": null,
  "account_id": DEIN_ERLOESKONTO_ID
}

group (Sammelposition mit Unterpositionen)

{
  "type": "group",
  "pos": "1",
  "is_optional": false,
  "text": "<strong>Gruppenname</strong><br /><br />Beschreibung",
  "show_pos_nr": true,
  "show_pos_prices": true,
  "positions": [
    { "type": "custom", "pos": "1.1", ... },
    { "type": "custom", "pos": "1.2", ... }
  ]
}

text (Textblock ohne Preis)

{
  "type": "text",
  "is_optional": false,
  "text": "<strong>Wichtig zu wissen</strong><br /><ul><li>...</li></ul>",
  "show_pos_nr": false
}

discount (Rabatt)

{
  "type": "discount",
  "text": "Rabattbezeichnung",
  "is_percentual": true,
  "value": 20
}

Offerte Status-Aktionen

POST /kb_offer/{id}/issue          # als "offen" markieren
POST /kb_offer/{id}/mark_as_sent   # als gesendet markieren
GET  /kb_offer/{id}/pdf            # PDF-Link abrufen

Bekannte Fallstricke


---