Proposals API
Offerten (Proposals) ermöglichen es Ihnen, professionelle Offerten zu erstellen, verwalten und als PDF zu generieren. Offerten können mit Kunden, Projekten und Kontakten verknüpft werden. Akzeptierte Offerten können in Rechnungen umgewandelt werden.
Proposal-Objekt
{
"id": 1523,
"user_id": 456,
"company_id": 123,
"customer_id": 410,
"contact_id": 175,
"project_id": 89,
"tax_rate_id": 12,
"number": "260001",
"title": "Webentwicklung Projektphase 1",
"lang": "de",
"date": "2024-01-15",
"valid_until": "2024-01-29",
"status": "sent",
"positions": [
{
"id": 1,
"type": "position",
"description": "Frontend-Entwicklung",
"amount": 10,
"price": 150.00,
"discount_amount": 0,
"discount_type": null,
"sum": 1500.00,
"unit": "h",
"order": 0
}
],
"remarks_top": null,
"remarks": "Gültig für 14 Tage ab Offertendatum.",
"currency": "CHF",
"total_value": 1500.00,
"discount_rate": null,
"discount_amount": 0,
"total_value_with_discount": 1500.00,
"vat_active": true,
"vat_included": false,
"vat_amount": 121.50,
"total_value_with_vat": 1621.50,
"final_value": 1621.50,
"document_settings": {},
"add_rounding_difference": false,
"rounding_difference": 0,
"with_signature": true,
"signature_remark": "Mit der Unterschrift wird diese Offerte zum Auftrag.",
"created_at": "2024-01-15T10:30:00.000000Z",
"updated_at": "2024-01-15T14:22:00.000000Z",
"formatted": {
"date": "15.01.2024"
},
"final_value_formatted": "1'621.50",
"valid_until_formatted": "29.01.2024",
"generated_title": "Offerte #260001",
"payableDays": 14,
"type": "proposal",
"overdue": false,
"is_shared": false,
"share_token": null
}Attribute
| Feld | Typ | Beschreibung |
|---|---|---|
id | integer | Eindeutige Offerten-ID |
user_id | integer | ID des Benutzers, der die Offerte erstellt hat |
company_id | integer | ID der zugehörigen Firma |
customer_id | integer | ID des Kunden |
contact_id | integer|null | ID des Kontakts beim Kunden |
project_id | integer|null | ID des verknüpften Projekts |
tax_rate_id | integer|null | ID des MwSt-Satzes |
number | string | Offertennummer |
title | string|null | Titel der Offerte |
lang | string | Sprache (de, en, fr, it) |
date | date | Offertendatum |
valid_until | date | Gültigkeitsdatum |
status | string | Status (siehe Status-Werte) |
positions | array | Offertenpositionen |
remarks_top | string|null | Bemerkungen oben |
remarks | string|null | Bemerkungen unten |
currency | string | Währung (CHF, EUR, USD, GBP) |
total_value | decimal | Zwischensumme (ohne Rabatt/MwSt) |
discount_rate | decimal|null | Rabatt in Prozent |
discount_amount | decimal | Rabattbetrag |
total_value_with_discount | decimal | Summe nach Rabatt |
vat_active | boolean | MwSt aktiviert |
vat_included | boolean | MwSt inklusiv |
vat_amount | decimal | MwSt-Betrag |
total_value_with_vat | decimal | Summe mit MwSt |
final_value | decimal | Endbetrag |
with_signature | boolean | Unterschriftsfeld anzeigen |
signature_remark | string|null | Text für Unterschriftsfeld |
overdue | boolean | Abgelaufen |
is_shared | boolean | Öffentlicher Link aktiv |
share_token | string|null | Token für öffentlichen Link |
Status-Werte
| Status | Beschreibung |
|---|---|
open | Entwurf/Offen |
sent | Versendet |
accepted | Akzeptiert |
declined | Abgelehnt |
confirmation | Auftragsbestätigung |
Positions-Objekt
{
"id": 1,
"type": "position",
"description": "Frontend-Entwicklung",
"amount": 10,
"price": 150.00,
"discount_amount": 0,
"discount_type": null,
"sum": 1500.00,
"unit": "h",
"order": 0,
"parent_id": null,
"formatted": {
"sum": "1'500.00"
}
}| Feld | Typ | Beschreibung |
|---|---|---|
type | string | Typ (position, text, collection) |
description | string|null | Beschreibung |
title | string|null | Titel (nur bei collection) |
amount | decimal | Menge |
price | decimal | Einzelpreis |
discount_amount | decimal | Rabatt |
discount_type | string|null | Rabatttyp (absolute, percentage) |
sum | decimal | Summe |
unit | string|null | Einheit (z.B. h, Stk.) |
order | integer | Reihenfolge |
parent_id | integer|null | ID der übergeordneten Collection |
Endpoints
Alle Offerten auflisten
GET /companies/{company_id}/proposalsRuft eine Liste aller Offerten der Firma ab.
Parameter
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
filter[id] | integer | - | Filtern nach spezifischer ID |
filter[customer_id] | integer | - | Filtern nach Kunde |
filter[project_id] | integer | - | Filtern nach Projekt |
filter[date] | string | - | Filtern nach Datum (Bereich, z.B. 2024-01-01,2024-12-31) |
filter[status] | string | - | Filtern nach Status |
Filter-Optionen für Status
| Wert | Beschreibung |
|---|---|
open | Offene/Versendete Offerten |
sent | Versendete Offerten |
accepted | Akzeptierte Offerten |
declined | Abgelehnte Offerten |
confirmation | Auftragsbestätigungen |
overdue | Abgelaufene Offerten |
all | Alle Offerten |
Response
{
"data": [
{
"id": 1523,
"number": "260001",
"title": "Webentwicklung Projektphase 1",
"date": "2024-01-15",
"valid_until": "2024-01-29",
"status": "sent",
"final_value": 1621.50,
"customer": {
"id": 410,
"name": "Muster AG"
}
}
],
"meta": {
"count": 1,
"total": 25,
"sum": 1621.50,
"sum_formatted": "1'621.50"
}
}Beispiel
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals"
# Mit Filtern
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals?filter[status]=open&filter[customer_id]=410"Neue Offerte erstellen
POST /companies/{company_id}/proposalsErstellt eine neue Offerte.
Request Body
{
"customer_id": 410,
"date": "2024-01-15",
"valid_until": "2024-01-29",
"positions": "[{\"description\":\"Frontend-Entwicklung\",\"amount\":10,\"price\":150}]",
"title": "Webentwicklung Projektphase 1",
"remarks": "Gültig für 14 Tage ab Offertendatum.",
"tax_rate_id": 12,
"lang": "de"
}Wichtige Felder
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
customer_id | integer | Ja | Kunde für die Offerte |
positions | string (JSON) | Nein | Positionen als JSON-String |
date | date | Nein | Offertendatum (Standard: heute) |
valid_until | date | Nein | Gültigkeitsdatum (Standard: + 14 Tage) |
number | string | Nein | Offertennummer (automatisch wenn nicht angegeben) |
title | string | Nein | Titel |
lang | string | Nein | Sprache (de, en, fr, it) |
remarks | string | Nein | Bemerkungen unten |
remarks_top | string | Nein | Bemerkungen oben |
discount_rate | decimal | Nein | Gesamtrabatt in Prozent (0-100) |
tax_rate_id | integer | Nein | MwSt-Satz ID |
project_id | integer | Nein | Verknüpftes Projekt |
contact_id | integer | Nein | Kontakt beim Kunden |
currency | string | Nein | Währung |
with_signature | boolean | Nein | Unterschriftsfeld anzeigen |
signature_remark | string | Nein | Text für Unterschriftsfeld |
Positionen-Format
[
{
"type": "position",
"description": "Entwicklung",
"amount": 10,
"price": 150,
"unit": "h"
},
{
"type": "text",
"text": "Zusätzliche Hinweise"
},
{
"type": "collection",
"title": "Paket A",
"positions": [
{
"description": "Unterposition 1",
"amount": 1,
"price": 100
}
]
}
]Response
HTTP/1.1 201 Created{
"id": 1524,
"number": "260002",
"status": "open",
"final_value": 1621.50,
"created_at": "2024-01-15T14:30:00.000000Z"
}Beispiel
curl -X POST \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": 410,
"positions": "[{\"description\":\"Entwicklung\",\"amount\":10,\"price\":150}]",
"tax_rate_id": 12
}' \
"https://app.milkee.ch/api/v2/companies/123/proposals"Einzelne Offerte abrufen
GET /companies/{company_id}/proposals/{proposal_id}Ruft die Details einer spezifischen Offerte ab.
Response
Die Response enthält das vollständige Proposal-Objekt mit allen geladenen Beziehungen:
customer- Kundeninformationen mit Kontaktencontact- Kontaktpersonproject- Verknüpftes Projekttax_rate- MwSt-Satzinvoices- Verknüpfte Rechnungenattachments- Anhänge
Beispiel
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523"Offerte aktualisieren
PUT /companies/{company_id}/proposals/{proposal_id}Aktualisiert eine bestehende Offerte.
Request Body
{
"title": "Webentwicklung Projektphase 1 (aktualisiert)",
"positions": "[{\"description\":\"Angepasste Position\",\"amount\":12,\"price\":150}]",
"discount_rate": 5
}Aktualisierbare Felder
number,title,langdate,valid_untilpositions,remarks,remarks_topdiscount_rate,currencycustomer_id,contact_id,project_idtax_rate_idwith_signature,signature_remark
Beispiel
curl -X PUT \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{"title": "Aktualisierter Titel", "discount_rate": 10}' \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523"Offerte löschen
DELETE /companies/{company_id}/proposals/{proposal_id}Löscht eine Offerte unwiderruflich.
Response
HTTP/1.1 204 No ContentStatus ändern (Mark As)
GET /companies/{company_id}/proposals/{proposal_id}/mark-asÄndert den Status einer Offerte. Die Parameter werden als Query-Parameter übergeben.
Erlaubte Status-Übergänge
open → sent
sent → open, accepted, declined
accepted → open, confirmation
declined → open
confirmation → acceptedAls versendet markieren
?status=sentAls akzeptiert markieren
?status=acceptedAls abgelehnt markieren
?status=declinedIn Auftragsbestätigung umwandeln
?status=confirmationAuftragsbestätigung
Bei der Umwandlung in eine Auftragsbestätigung wird das Gültigkeitsdatum ausgeblendet und der Dokumenttitel ändert sich zu "Bestätigung".
Beispiele
# Als versendet markieren
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523/mark-as?status=sent"
# Als akzeptiert markieren
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523/mark-as?status=accepted"
# In Auftragsbestätigung umwandeln
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523/mark-as?status=confirmation"PDF herunterladen
GET /companies/{company_id}/proposals/{proposal_id}/pdfGeneriert und liefert die Offerte als PDF.
Response
{
"base64": "data:application/pdf;base64,JVBERi0xLjQK...",
"fileName": "Offerte Muster AG vom 15.01.2024.pdf"
}Beispiel
curl -H "Authorization: Bearer 1|abcdef123456789..." \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523/pdf"Nächste Offertennummer abrufen
GET /companies/{company_id}/proposals/numberGibt die nächste freie Offertennummer zurück.
Response
"260003"Teilrechnungen aus Offerte generieren
POST /companies/{company_id}/proposals/{proposal_id}/split-invoicesGeneriert mehrere Rechnungen aus einer Offerte basierend auf definierten Teilbeträgen.
Request Body
{
"splits": [
{
"title": "Anzahlung 30%",
"sum": 486.45
},
{
"title": "Schlusszahlung 70%",
"sum": 1135.05
}
]
}Summenvalidierung
Die Summe aller Teilbeträge muss exakt dem Offertenbetrag (ohne MwSt wenn MwSt exklusiv) entsprechen.
Response
[
{
"id": 21276,
"number": "260003",
"title": "Anzahlung 30%",
"final_value": 486.45
},
{
"id": 21277,
"number": "260004",
"title": "Schlusszahlung 70%",
"final_value": 1135.05
}
]Beispiel
curl -X POST \
-H "Authorization: Bearer 1|abcdef123456789..." \
-H "Content-Type: application/json" \
-d '{
"splits": [
{"title": "Anzahlung 30%", "sum": 486.45},
{"title": "Schlusszahlung 70%", "sum": 1135.05}
]
}' \
"https://app.milkee.ch/api/v2/companies/123/proposals/1523/split-invoices"Batch-Operationen
Mehrere Offerten aktualisieren
PATCH /companies/{company_id}/proposals/batch{
"ids": [1523, 1524, 1525],
"updateData": {
"discount_rate": 5
}
}Mehrere Offerten löschen
DELETE /companies/{company_id}/proposals/batch{
"ids": [1523, 1524]
}Mehrere Status ändern
POST /companies/{company_id}/proposals/batch/mark-as{
"ids": [1523, 1524],
"updateData": {
"status": "sent"
}
}Fehlertoleranz
Bei ungültigen Status-Übergängen werden betroffene Offerten übersprungen, andere werden verarbeitet.
Offertenpositionen API
Positionen können auch einzeln über dedizierte Endpoints verwaltet werden.
Positionen auflisten
GET /companies/{company_id}/proposals/{proposal_id}/positionsPosition erstellen
POST /companies/{company_id}/proposals/{proposal_id}/positions{
"type": "position",
"description": "Neue Position",
"amount": 5,
"price": 100,
"unit": "h"
}Position aktualisieren
PUT /companies/{company_id}/proposals/{proposal_id}/positions/{position_id}Position löschen
DELETE /companies/{company_id}/proposals/{proposal_id}/positions/{position_id}Aktivitäten
GET /companies/{company_id}/proposals/{proposal_id}/activitiesGibt die Aktivitätshistorie einer Offerte zurück.
{
"2024-01-15 10:30:00": {
"date": "15.01.2024",
"message": "Offerte wurde erstellt."
},
"2024-01-15 14:22:00": {
"date": "15.01.2024",
"message": "Offerte wurde als versendet markiert.",
"color": "orange"
},
"2024-01-18 09:15:00": {
"date": "18.01.2024",
"message": "Offerte wurde als akzeptiert markiert.",
"color": "green"
}
}Mögliche Aktivitätsmeldungen
| Meldung | Beschreibung |
|---|---|
Offerte wurde erstellt. | Offerte wurde angelegt |
Offerte wurde aktualisiert. | Offerte wurde bearbeitet |
Offerte wurde als offen markiert. | Status auf offen gesetzt |
Offerte wurde als versendet markiert. | Status auf versendet gesetzt |
Offerte wurde als akzeptiert markiert. | Status auf akzeptiert gesetzt |
Offerte wurde als abgelehnt markiert. | Status auf abgelehnt gesetzt |
Offerte wurde in Bestätigung umgewandelt. | In Auftragsbestätigung konvertiert |
Offerte wurde über öffentlichen Link angeschaut. | Kunde hat Offerte via Link angesehen |
Offerte wurde vom Kunden akzeptiert. | Kunde hat via Link akzeptiert |
Offerte wurde vom Kunden abgelehnt. | Kunde hat via Link abgelehnt |
Dokument-Anhänge
Dokument anhängen
POST /companies/{company_id}/proposals/{proposal_id}/attachHängt ein Dokument an die Offerte an.
Request Body (multipart/form-data)
| Feld | Typ | Beschreibung |
|---|---|---|
file | file | Die anzuhängende Datei |
Dokument entfernen
POST /companies/{company_id}/proposals/{proposal_id}/detach/{file_id}Entfernt einen Anhang von der Offerte.
Fehlerbehandlung
400 Bad Request
{
"message": "Offerte kann nicht gelöscht werden."
}422 Validation Error
{
"message": "The given data was invalid.",
"errors": {
"customer_id": ["Kunde ist erforderlich."],
"positions": ["Positionen müssen ein gültiges JSON sein."]
}
}Split-Invoice Fehler
{
"message": "Die Summe der Teilrechnungen müssen 100% des Offertenbetrags ergeben."
}Best Practices
Offerte mit Unterschrift erstellen
const proposal = await api.post(`/companies/${companyId}/proposals`, {
customer_id: customerId,
title: 'Webprojekt Phase 1',
positions: JSON.stringify([
{
description: 'Konzept und Design',
amount: 20,
price: 150,
unit: 'h'
},
{
description: 'Entwicklung',
amount: 40,
price: 150,
unit: 'h'
}
]),
with_signature: true,
signature_remark: 'Mit der Unterschrift erteilen Sie uns den Auftrag.'
});Offerte-Workflow
async function processProposal(companyId, proposalId) {
// Offerte versenden (GET mit Query-Parametern)
await api.get(`/companies/${companyId}/proposals/${proposalId}/mark-as`, {
params: { status: 'sent' }
});
// Nach Kundenakzeptanz
await api.get(`/companies/${companyId}/proposals/${proposalId}/mark-as`, {
params: { status: 'accepted' }
});
// Optional: In Auftragsbestätigung umwandeln
await api.get(`/companies/${companyId}/proposals/${proposalId}/mark-as`, {
params: { status: 'confirmation' }
});
}Offerte in Teilrechnungen aufteilen
async function splitProposalIntoInvoices(companyId, proposalId, proposal) {
const total = proposal.final_value - (proposal.vat_included ? 0 : proposal.vat_amount);
const invoices = await api.post(
`/companies/${companyId}/proposals/${proposalId}/split-invoices`,
{
splits: [
{ title: 'Anzahlung 30%', sum: Math.round(total * 0.3 * 100) / 100 },
{ title: 'Schlusszahlung 70%', sum: Math.round(total * 0.7 * 100) / 100 }
]
}
);
return invoices;
}Offerte-Status prüfen
function isProposalActionable(proposal) {
// Offerte kann akzeptiert/abgelehnt werden wenn offen oder versendet
return ['open', 'sent'].includes(proposal.status);
}
function isProposalExpired(proposal) {
const validUntil = new Date(proposal.valid_until);
return validUntil < new Date() && !['accepted', 'declined', 'confirmation'].includes(proposal.status);
}Nächste Schritte
- Invoices API - Rechnungen verwalten
- Customers API - Kunden verwalten
- Projects API - Projekte verwalten
- Webhooks einrichten
