Gå til hovedinnhold

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
Legg merke til prefikset /_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/me

To scopes styrer tilgangen:

ScopeGir tilgang til
partner:read/partner/me, /partner/customers, /partner/deals (GET), /partner/statements
partner:writeRegistrere 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 med partner: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/deals

På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" }
StatusBetydning
201Registrert
400Avvist – svaret sier hvilken sjekk som feilet
429Den 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.../customer

På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.

Beløpsfeltene er desimalstrenger, returnert nøyaktig slik de er lagret. Ikke tolk dem som flyttall og ikke avrund dem på nytt, ellers blir avstemmingen din uenig med oppgjørsoppstillingen den kom fra.

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.