Partner-API
Met de Partner-API voert een partner de hele commerciële flow uit vanuit zijn eigen systemen: een deal registreren zodra er een opportunity is aangemaakt, een klant op het platform brengen wanneer die is binnengehaald, en margeafrekeningen afstemmen met de eigen boekhouding.
Wat de flow commercieel betekent (beschermingsperiodes, conflicten, hoe de marge wordt berekend), lees je eerst in de gids Partnerprogramma. Deze pagina beschrijft de techniek.
Basis-URL:
https://frem.sh/_app/api/v1/_app. frem.sh/api/v1/... is de eigen API van Forgejo, een ander oppervlak met andere tokens. Een partneraanroep die daarheen gaat, mislukt niet op een opvallende manier: hij wordt gewoon door Forgejo beantwoord.Authenticatie
Partner-endpoints accepteren een persoonlijk toegangstoken van fremforge als bearer-token:
curl -H "Authorization: Bearer $FF_TOKEN" \
https://frem.sh/_app/api/v1/partner/meTwee scopes bepalen de toegang:
| Scope | Geeft toegang tot |
|---|---|
partner:read | /partner/me, /partner/customers, /partner/deals (GET), /partner/statements |
partner:write | Deals registreren, enterprisevoorwaarden aanvragen, klanten aanmaken |
Een integratie die alleen leest, zoals een dashboard of een afstemmingstaak, hoort alleen partner:read te hebben.
Een token krijgen
Maak er een aan in het partnerportaal onder Jouw toegang, of met een bestaand token dat de scope tokens:manage heeft:
curl -X POST -H "Authorization: Bearer $FF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"crm-integration","scopes":["partner:read","partner:write"],"ttl_days":90}' \
https://frem.sh/_app/api/v1/auth/tokens{ "token": "ffp_...", "id": "0192...", "expires_at": "2026-11-14T09:12:00Z" }ttl_days is standaard 30 en hoogstens 90, dus een integratie die lang draait, heeft een rotatieplan nodig in plaats van een token dat je instelt en vergeet. De waarde van het token wordt één keer getoond. Zie Tokenbeheer voor roteren en intrekken.
Twee manieren waarop een aanroep wordt geweigerd
Ze betekenen iets anders, en het loont om ze in je foutafhandeling uit elkaar te houden:
403 not_a_partner: het token is geldig, maar de eigenaar ervan is geen partnergebruiker. Meestal het verkeerde token.403 insufficient-scope: de juiste identiteit, maar de scope ontbreekt. Maak het token opnieuw aan metpartner:write.
Geen van beide spellingen is een tikfout op deze pagina: not_a_partner gebruikt een underscore, insufficient-scope een koppelteken. Match op de exacte string.
Partnergegevens gaan bewust over tenants heen: deze endpoints zijn niet aan een organisatie gebonden, en de eigen tenant van een partner hoort niet bij de tenants die hij verder mag beheren.
GET /partner/me
Namens wie je handelt, en of dealregistratie op dit moment voor je is gepauzeerd.
curl -H "Authorization: Bearer $FF_TOKEN" \
https://frem.sh/_app/api/v1/partner/me{
"partner_id": "3f2a...",
"partner_name": "Nordic Resell ApS",
"username": "anna",
"agreement_status": "active"
}Met agreement_status op paused authenticeer je nog steeds en wordt elke leesaanroep nog beantwoord. Pauzeren is een maatregel tegen misbruik van registratie, geen blokkade. Behandel het dus niet als een authenticatiefout.
GET /partner/customers
De klanten die op dit moment aan je zijn toegewezen.
{ "customers": [ { "tenant_id": "9c1e...", "org_slug": "acme" } ] }De marge telt vanaf de toewijzingsdatum, dus een klant die laat is toegewezen, levert niets op voor de maanden daarvoor.
GET /partner/deals
Elke deal die je hebt geregistreerd, de nieuwste eerst.
{
"deals": [
{
"id": "7b40...",
"end_customer": "Acme A/S",
"country": "DK",
"status": "approved",
"conflict_state": "clear",
"tenant_id": null,
"protection_expires_at": "2026-11-14T00:00:00Z"
}
]
}protection_expires_at is het einde van de beschermingsperiode van 90 dagen die de registratie heeft opgeleverd. We mailen je 14 dagen en 3 dagen voordat die afloopt.
conflict_state met de waarde flagged betekent dat een andere partner hetzelfde bedrijf heeft geregistreerd. We beslissen daarover op basis van aantoonbare verkoopactiviteit, nooit op basis van wie er het eerst was, en we maken de andere partner niet bekend.
POST /partner/deals
Registreer een prospect en start de beschermingsperiode. Vereist partner:write.
curl -X POST -H "Authorization: Bearer $FF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"end_customer_legal_name": "Acme A/S",
"end_customer_country": "DK",
"end_customer_registration_number": "12345678",
"end_customer_contact_email": "cto@acme.example",
"estimated_seats": 25,
"expected_close_date": "2026-11-01",
"sales_activity_evidence": "Demo held 2026-08-12; POC agreed with their CTO."
}' \
https://frem.sh/_app/api/v1/partner/dealsVerplicht: end_customer_legal_name, end_customer_country (ISO 3166-1 alpha-2, in hoofdletters).
{ "id": "7b40...", "flagged": false, "pinned_price_list_version": "2026-06" }| Status | Betekenis |
|---|---|
201 | Geregistreerd |
400 | Geweigerd; de body noemt welke controle is mislukt |
429 | Dagelijkse registratielimiet bereikt |
flagged: true is een 201, geen fout. De deal is geregistreerd; een andere partner heeft hetzelfde bedrijf geregistreerd en wij beslissen erover. Probeer het niet opnieuw: opnieuw proberen verbruikt je dagelijkse tegoed en verandert niets.Vul sales_activity_evidence zorgvuldig in. Dat is het bewijs waarop een conflict wordt beslist.
POST /partner/deals/{dealId}/enterprise-request
Vraag onderhandelde prijzen per gebruiker aan voordat je de klant aanmaakt. Vereist partner:write.
curl -X POST -H "Authorization: Bearer $FF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note":"Needs 400 seats and annual invoicing; competing against a US vendor."}' \
https://frem.sh/_app/api/v1/partner/deals/7b40.../enterprise-request{ "ok": true, "recorded": true }Geeft altijd 200 terug. recorded: false betekent dat het verzoek niet is toegepast, en maakt daarbij bewust geen onderscheid tussen “al aangevraagd” en “niet jouw deal”.
De eerste schrijfactie wint: twee keer aanvragen verschuift de vastgelegde datum niet en vervangt de notitie niet. Alleen geaccepteerd zolang de deal is goedgekeurd of gewonnen en er nog geen klant bestaat; daarna is de tenant echt en is het omzetten ervan een actie voor de operator.
POST /partner/deals/{dealId}/customer
Breng de klant van een deal die je hebt gewonnen op het platform. Vereist partner:write.
curl -X POST -H "Authorization: Bearer $FF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"org_slug": "acme",
"display_name": "Acme A/S",
"admin_email": "cto@acme.example"
}' \
https://frem.sh/_app/api/v1/partner/deals/7b40.../customerVerplicht: org_slug (kleine letters, DNS-veilig, 3–30 tekens; het wordt frem.sh/<slug> en kan niet worden gewijzigd), display_name, admin_email.
Dit maakt de organisatie aan, nodigt de beheerder per e-mail uit en wijst de klant aan jou toe, zodat de marge telt vanaf de eerste factuur.
De klant begint met een proefperiode. Het abonnement is bewust geen parameter: het kiezen ervan zou neerkomen op het toekennen van commerciële voorwaarden. Het antwoord bevat billing_setup_url, waar de klant een betaalmethode toevoegt en overstapt; dezelfde link wordt naar de klant gemaild.
Geweigerd als de deal niet van jou is, niet is goedgekeurd of gewonnen, al een klant heeft, of als registratie voor je is gepauzeerd. 429 als de dagelijkse limiet voor het aanmaken van klanten is bereikt.
GET /partner/statements
Je margeafrekeningen, de nieuwste periode eerst.
{
"statements": [
{
"id": "aa19...",
"period_start": "2026-07-01",
"period_end": "2026-07-31",
"currency": "EUR",
"margin_eur": "1284.50",
"total_invoiceable_eur": "1284.50",
"sent_at": "2026-08-01T06:00:00Z",
"partner_invoice_ref": "INV-2026-114",
"paid_at": null
}
]
}Afrekeningen worden op de 1e opgesteld voor de voorgaande kalendermaand.
De vereffening verloopt in de volgorde sent_at → partner_invoice_ref → paid_at. Er wordt niets betaald zolang er geen factuurreferentie van de partner is, omdat jouw factuur het document is waartegen de betaling plaatsvindt. Betaling volgt binnen 30 dagen na ontvangst ervan.
Een uitgewerkte flow
Registreren vanuit een CRM zodra er een opportunity is aangemaakt, en daarna de klant aanmaken wanneer die is binnengehaald:
BASE=https://frem.sh/_app/api/v1
AUTH="Authorization: Bearer $FF_TOKEN"
# 1. Register as soon as the opportunity exists — this is what starts protection.
DEAL=$(curl -sSf -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"end_customer_legal_name":"Acme A/S","end_customer_country":"DK",
"sales_activity_evidence":"Discovery call 2026-08-12"}' \
"$BASE/partner/deals")
DEAL_ID=$(printf '%s' "$DEAL" | jq -r .id)
# `flagged` is informational, not a failure — carry on, do not retry.
printf '%s' "$DEAL" | jq -r 'if .flagged then "conflict registered; we will resolve it" else "protected" end'
# 2. When it is won, create the customer.
curl -sSf -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"org_slug":"acme","display_name":"Acme A/S","admin_email":"cto@acme.example"}' \
"$BASE/partner/deals/$DEAL_ID/customer" | jq -r .billing_setup_url
# 3. Reconcile, as strings.
curl -sSf -H "$AUTH" "$BASE/partner/statements" \
| jq -r '.statements[] | "\(.period_start) \(.margin_eur) \(.currency) paid=\(.paid_at // "no")"'Fouten
Partner-endpoints gebruiken de platformbrede vorm application/problem+json. De URL in type verwijst naar een pagina die de specifieke fout beschrijft; zie API-fouten.
De volledige specificatie
Deze endpoints maken deel uit van het gepubliceerde OpenAPI-document, onder de tag Partner:
De specificatie wordt gegenereerd uit de draaiende dienst en is leidend waar deze pagina en de specificatie elkaar tegenspreken.