Partner-API
Med Partner-API’et kan en partner køre hele det kommercielle forløb fra sine egne systemer: registrere et deal i det øjeblik, en salgsmulighed oprettes, oprette kunden på platformen, når dealet er vundet, og afstemme marginopgørelser mod sit eget regnskab.
Hvad forløbet betyder kommercielt – beskyttelsesvinduer, konflikter, hvordan margin beregnes – står i vejledningen til partnerprogrammet, som du bør læse først. Denne side handler om mekanikken.
Basis-URL:
https://frem.sh/_app/api/v1/_app. frem.sh/api/v1/... er Forgejos eget API, som er en anden overflade med andre tokens. Et partnerkald, der sendes dertil, fejler ikke på en tydelig måde – det bliver bare besvaret af Forgejo.Godkendelse
Partner-endpoints tager et personligt fremforge-adgangstoken som bearer-token:
curl -H "Authorization: Bearer $FF_TOKEN" \
https://frem.sh/_app/api/v1/partner/meTo scopes styrer adgangen:
| Scope | Giver adgang til |
|---|---|
partner:read | /partner/me, /partner/customers, /partner/deals (GET), /partner/statements |
partner:write | Registrering af deals, anmodning om enterprisevilkår, oprettelse af kunder |
En integration, der kun læser – et dashboard, et afstemningsjob – bør kun have partner:read.
Sådan får du et token
Udsted et i partnerportalen under Din adgang, eller fra et eksisterende token med scopet tokens:manage:
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 er som standard 30 og højst 90, så en integration, der kører længe, skal have en plan for rotation i stedet for et token, den kan sætte op og glemme. Tokenværdien vises én gang. Se Administration af tokens om rotation og tilbagekaldelse.
To måder, et kald kan blive afvist på
De betyder forskellige ting, og det er værd at skelne mellem dem i din fejlhåndtering:
403 not_a_partner– tokenet er gyldigt, men dets ejer er ikke en partnerbruger. Som regel det forkerte token.403 insufficient-scope– den rigtige identitet, men scopet mangler. Udsted et nyt medpartner:write.
Ingen af stavemåderne er en tastefejl på denne side: not_a_partner bruger understregning, insufficient-scope en bindestreg. Match på den nøjagtige streng.
Partnerdata går bevidst på tværs af tenants: disse endpoints er ikke knyttet til en organisation, og en partners egen tenant er udelukket fra de tenants, partneren ellers kan administrere.
GET /partner/me
Hvem du handler som, og om dealregistrering lige nu er sat på pause for dig.
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"
}agreement_status med værdien paused godkendes stadig og leverer stadig alle læsninger. En pause er en kontrol mod misbrug af registrering, ikke en udelukkelse – behandl den ikke som en fejl i godkendelsen.
GET /partner/customers
De kunder, der i øjeblikket er tildelt dig.
{ "customers": [ { "tenant_id": "9c1e...", "org_slug": "acme" } ] }Margin tæller fra tildelingsdatoen, så en kunde, der tildeles sent, giver intet for månederne før.
GET /partner/deals
Alle deals, du har registreret, med de nyeste først.
{
"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 er slutningen på det beskyttelsesvindue på 90 dage, som registreringen gav. Vi sender dig en e-mail 14 dage og 3 dage, før det udløber.
conflict_state med værdien flagged betyder, at en anden partner har registreret den samme virksomhed. Vi afgør den slags ud fra dokumentation for salgsaktivitet, aldrig ud fra, hvem der kom først, og vi oplyser ikke, hvem den anden partner er.
POST /partner/deals
Registrér en mulig kunde, og start beskyttelsesvinduet. Kræver 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/dealsPåkrævet: end_customer_legal_name, end_customer_country (ISO 3166-1 alpha-2, store bogstaver).
{ "id": "7b40...", "flagged": false, "pinned_price_list_version": "2026-06" }| Status | Betydning |
|---|---|
201 | Registreret |
400 | Afvist – indholdet angiver, hvilket tjek der fejlede |
429 | Det daglige loft over registreringer er nået |
flagged: true er en 201, ikke en fejl. Dealet er registreret; en anden partner har registreret den samme virksomhed, og vi afgør det. Prøv ikke igen – et nyt forsøg bruger af din daglige kvote og ændrer intet.Udfyld sales_activity_evidence ordentligt. Det er den dokumentation, en konflikt afgøres ud fra.
POST /partner/deals/{dealId}/enterprise-request
Bed om forhandlede priser pr. bruger, før du opretter kunden. Kræver 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 }Svarer altid 200. recorded: false betyder, at anmodningen ikke blev registreret – bevidst uden at skelne mellem “allerede anmodet” og “ikke dit deal”.
Den første skrivning gælder: at spørge to gange flytter ikke den registrerede dato og erstatter ikke noten. Accepteres kun, mens dealet er godkendt eller vundet, og der endnu ikke findes en kunde; derefter er tenanten virkelig, og at konvertere den er en opgave for operatøren.
POST /partner/deals/{dealId}/customer
Opret kunden på platformen for et deal, du har vundet. Kræver 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.../customerPåkrævet: org_slug (små bogstaver, DNS-sikker, 3–30 tegn – den bliver til frem.sh/<slug> og kan ikke ændres), display_name, admin_email.
Det opretter organisationen, inviterer administratoren via e-mail og tildeler kunden til dig, så margin tæller fra kundens første faktura.
Kunden starter på en prøveperiode. Abonnementet er bevidst ikke en parameter – at vælge det ville være at give kommercielle vilkår. Svaret indeholder billing_setup_url, hvor kunden tilføjer en betalingsmetode og konverterer; det samme link sendes til kunden via e-mail.
Afvises, når dealet ikke er dit, ikke er godkendt eller vundet, allerede har en kunde, eller når registrering er sat på pause for dig. 429, når det daglige loft over oprettelse af kunder er nået.
GET /partner/statements
Dine marginopgørelser med den nyeste periode først.
{
"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
}
]
}Opgørelser dannes den 1. i måneden for den foregående kalendermåned.
Afregning sker i rækkefølgen sent_at → partner_invoice_ref → paid_at. Intet udbetales, før der findes en reference til en partnerfaktura, fordi din faktura er det bilag, betalingen sker imod. Betaling sker 30 dage efter, at vi har modtaget den.
Et gennemgået forløb
Registrering fra et CRM i det øjeblik, en salgsmulighed oprettes, og oprettelse af kunden, når den er vundet:
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")"'Fejl
Partner-endpoints bruger platformens fælles application/problem+json-form. type-URL’en fører til en side, der beskriver den konkrete fejl – se API-fejl.
Den fulde specifikation
Disse endpoints er en del af det offentliggjorte OpenAPI-dokument under tagget Partner:
Specifikationen genereres ud fra den kørende tjeneste og er den gældende, hvor den og denne side er uenige.