Partner API
Med Partner API kan en partner kjøre hele den kommersielle flyten fra sine egne systemer: registrere en avtale i det øyeblikket en mulighet opprettes, få kunden inn på plattformen når den er vunnet, og avstemme oppgjørsoppstillingene for margin mot sitt eget regnskap.
Hva flyten betyr kommersielt – beskyttelsesperioder, konflikter, hvordan marginen beregnes – leser du først i veiledningen Partnerprogram. Denne siden handler om mekanikken.
Base-URL:
https://frem.sh/_app/api/v1/_app. frem.sh/api/v1/... er Forgejos eget API, som er en annen flate med andre tokener. Et partnerkall som sendes dit, feiler ikke på en åpenbar måte – det blir ganske enkelt besvart av Forgejo.Autentisering
Partnerendepunktene tar et personlig tilgangstoken fra fremforge som bearer-token:
curl -H "Authorization: Bearer $FF_TOKEN" \
https://frem.sh/_app/api/v1/partner/meTo scopes styrer tilgangen:
| Scope | Gir tilgang til |
|---|---|
partner:read | /partner/me, /partner/customers, /partner/deals (GET), /partner/statements |
partner:write | Registrere avtaler, be om enterprise-vilkår, opprette kunder |
En integrasjon som bare leser – et dashbord, en avstemmingsjobb – bør bare ha partner:read.
Få et token
Utsted et i partnerportalen under Din tilgang, 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 30 som standard og høyst 90, så en integrasjon som skal kjøre lenge, trenger en plan for rotasjon i stedet for et token den kan sette opp og glemme. Verdien til tokenet vises én gang. Se Administrasjon av tokener for rotasjon og tilbakekalling.
To måter et kall kan avvises på
De betyr forskjellige ting, og det er verdt å skille dem i feilhåndteringen din:
403 not_a_partner– tokenet er gyldig, men eieren er ikke en partnerbruker. Som regel feil token.403 insufficient-scope– riktig identitet, men scopet mangler. Utsted tokenet på nytt medpartner:write.
Ingen av skrivemåtene er en skrivefeil på denne siden: not_a_partner bruker understrek, insufficient-scope bindestrek. Match på den nøyaktige strengen.
Partnerdata går med vilje på tvers av kunder: disse endepunktene er ikke avgrenset til en organisasjon, og partnerens egen kunde er utelatt fra kundene partneren ellers kan administrere.
GET /partner/me
Hvem du handler som, og om avtaleregistrering er satt på pause for deg akkurat nå.
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"
}Med agreement_status lik paused autentiseres du fortsatt og får fortsatt svar på alle lesekall. Pause er et vern mot misbruk av registreringen, ikke en utestengelse – ikke behandle det som en autentiseringsfeil.
GET /partner/customers
Kundene som er tildelt deg nå.
{ "customers": [ { "tenant_id": "9c1e...", "org_slug": "acme" } ] }Marginen regnes fra tildelingsdatoen, så en kunde som tildeles sent, gir ingenting for månedene før.
GET /partner/deals
Alle avtalene du har registrert, 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 slutten på beskyttelsesperioden på 90 dager som registreringen ga. Vi sender deg e-post 14 dager og 3 dager før den utløper.
conflict_state lik flagged betyr at en annen partner har registrert det samme selskapet. Vi avgjør slike saker ut fra dokumentert salgsaktivitet, aldri ut fra hvem som var først, og vi oppgir ikke hvem den andre partneren er.
POST /partner/deals
Registrer et prospekt og start beskyttelsesperioden. Krever 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åkrevd: end_customer_legal_name, end_customer_country (ISO 3166-1 alpha-2, store bokstaver).
{ "id": "7b40...", "flagged": false, "pinned_price_list_version": "2026-06" }| Status | Betydning |
|---|---|
201 | Registrert |
400 | Avvist – svaret sier hvilken sjekk som feilet |
429 | Den daglige grensen for registreringer er nådd |
flagged: true er en 201, ikke en feil. Avtalen er registrert; en annen partner har registrert det samme selskapet, og vi avgjør saken. Ikke prøv igjen – et nytt forsøk bruker av den daglige kvoten din og endrer ingenting.Fyll ut sales_activity_evidence ordentlig. Det er dokumentasjonen en konflikt avgjøres på.
POST /partner/deals/{dealId}/enterprise-request
Be om forhandlede priser per bruker før du oppretter kunden. Krever 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 alltid 200. recorded: false betyr at forespørselen ikke ble registrert – med vilje uten å skille mellom «allerede spurt» og «ikke din avtale».
Første skriving gjelder: å spørre to ganger flytter ikke den registrerte datoen og erstatter ikke notatet. Godtas bare mens avtalen er godkjent eller vunnet og ingen kunde finnes ennå; etter det er kunden reell, og å konvertere den er en operatørhandling.
POST /partner/deals/{dealId}/customer
Få kunden inn på plattformen for en avtale du har vunnet. Krever 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åkrevd: org_slug (små bokstaver, DNS-sikker, 3–30 tegn – den blir frem.sh/<slug> og kan ikke endres), display_name, admin_email.
Dette oppretter organisasjonen, inviterer administratoren på e-post og tildeler kunden til deg, slik at marginen regnes fra den første fakturaen.
Kunden starter på en prøveperiode. Planen er med vilje ikke en parameter – å velge den ville vært å gi kommersielle vilkår. Svaret inneholder billing_setup_url, der kunden legger til en betalingsmåte og går over til betalt plan; den samme lenken sendes til kunden på e-post.
Avvises når avtalen ikke er din, ikke er godkjent eller vunnet, allerede har en kunde, eller når registrering er satt på pause for deg. 429 når den daglige grensen for oppretting av kunder er nådd.
GET /partner/statements
Oppgjørsoppstillingene dine for margin, den nyeste perioden 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
}
]
}Oppgjørsoppstillingene lages den 1. i måneden for forrige kalendermåned.
Oppgjøret skjer i rekkefølgen sent_at → partner_invoice_ref → paid_at. Ingenting utbetales før det finnes en fakturareferanse fra partneren, fordi fakturaen din er dokumentet betalingen gjøres mot. Betaling skjer 30 dager etter at vi har mottatt den.
En gjennomgått flyt
Registrering fra et CRM i det øyeblikket en mulighet opprettes, og så oppretting av kunden når den er vunnet:
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")"'Feil
Partnerendepunktene bruker den samme application/problem+json-formen som resten av plattformen. URL-en i type fører til en side som beskriver den konkrete feilen – se API-feil.
Hele spesifikasjonen
Disse endepunktene er en del av det publiserte OpenAPI-dokumentet, under taggen Partner:
Spesifikasjonen genereres fra tjenesten som kjører, og er den som gjelder der den og denne siden er uenige.