Partner-API
Med Partner-API:t kan en partner sköta hela det kommersiella flödet från sina egna system: registrera en affär i samma stund som en möjlighet skapas, ta in en kund på plattformen när affären är vunnen och stämma av avräkningar av marginal mot sin egen bokföring.
Läs först guiden om partnerprogrammet för vad flödet betyder kommersiellt: skyddsfönster, konflikter och hur marginalen beräknas. Den här sidan handlar om mekaniken.
Bas-URL:
https://frem.sh/_app/api/v1/_app. frem.sh/api/v1/... är Forgejos eget API, som är en annan yta med andra tokens. Ett partneranrop som skickas dit misslyckas inte på något uppenbart sätt; det besvaras helt enkelt av Forgejo.Autentisering
Partner-endpoints tar en personlig åtkomsttoken för fremforge som bearer-token:
curl -H "Authorization: Bearer $FF_TOKEN" \
https://frem.sh/_app/api/v1/partner/meTvå scopes styr åtkomsten:
| Scope | Ger |
|---|---|
partner:read | /partner/me, /partner/customers, /partner/deals (GET), /partner/statements |
partner:write | Registrera affärer, begära enterprise-villkor, skapa kunder |
En integration som bara läser, som en instrumentpanel eller ett avstämningsjobb, bör bara ha partner:read.
Skaffa en token
Utfärda en i partnerportalen under Din åtkomst, eller från en befintlig 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 är som standard 30 och högst 90, så en integration som körs länge behöver en plan för rotation snarare än en token som den kan ställa in och glömma. Tokenvärdet visas en gång. Se Hantering av tokens för rotation och återkallning.
Två sätt ett anrop kan nekas på
De betyder olika saker och är värda att skilja på i din felhantering:
403 not_a_partner: token är giltig, men dess ägare är inte en partneranvändare. Oftast fel token.403 insufficient-scope: rätt identitet, men scopet saknas. Utfärda en ny token medpartner:write.
Ingen av stavningarna är ett stavfel på den här sidan: not_a_partner använder understreck, insufficient-scope bindestreck. Matcha på den exakta strängen.
Partnerdata är avsiktligt tenant-övergripande: de här endpointerna är inte begränsade till en organisation, och en partners egen tenant är undantagen från de tenants som partnern annars kan administrera.
GET /partner/me
Vem du agerar som, och om affärsregistrering för närvarande är pausad för 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ärdet paused autentiseras fortfarande och betjänar fortfarande varje läsning. Pausning är en kontroll mot missbruk av registreringen, inte en utelåsning; behandla det inte som ett autentiseringsfel.
GET /partner/customers
De kunder som för närvarande är tilldelade dig.
{ "customers": [ { "tenant_id": "9c1e...", "org_slug": "acme" } ] }Marginalen räknas från tilldelningsdatumet, så en kund som tilldelas sent ger ingenting för månaderna innan.
GET /partner/deals
Alla affärer du har registrerat, nyaste 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 är slutet på det 90-dagarsfönster som registreringen gav. Vi mejlar dig 14 dagar och 3 dagar innan det löper ut.
conflict_state med värdet flagged betyder att en annan partner har registrerat samma företag. Vi avgör sådana fall utifrån belägg för säljaktivitet, aldrig utifrån vem som var först, och vi avslöjar inte den andra partnern.
POST /partner/deals
Registrera ett prospekt och starta skyddsfönstret. 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/dealsObligatoriskt: end_customer_legal_name, end_customer_country (ISO 3166-1 alpha-2, versaler).
{ "id": "7b40...", "flagged": false, "pinned_price_list_version": "2026-06" }| Status | Betydelse |
|---|---|
201 | Registrerad |
400 | Nekad: innehållet anger vilken kontroll som misslyckades |
429 | Dagligt tak för registreringar nått |
flagged: true är en 201, inte ett fel. Affären är registrerad; en annan partner har registrerat samma företag och vi kommer att avgöra det. Försök inte igen: nya försök förbrukar din dagliga kvot och ändrar ingenting.Fyll i sales_activity_evidence ordentligt. Det är de belägg som en konflikt avgörs utifrån.
POST /partner/deals/{dealId}/enterprise-request
Be om förhandlade priser per användare innan du skapar 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 }Svarar alltid 200. recorded: false betyder att begäran inte gällde, och skiljer avsiktligt inte mellan “redan begärt” och “inte din affär”.
Den första skrivningen gäller: att fråga två gånger flyttar inte det registrerade datumet och ersätter inte anteckningen. Godtas bara medan affären är godkänd eller vunnen och ingen kund finns än; därefter är tenanten verklig och att konvertera den är en operatörsåtgärd.
POST /partner/deals/{dealId}/customer
Ta in kunden på plattformen för en affär du har vunnit. 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.../customerObligatoriskt: org_slug (gemener och DNS-säkert, 3–30 tecken; det blir frem.sh/<slug> och kan inte ändras), display_name, admin_email.
Det här skapar organisationen, bjuder in administratören via e-post och tilldelar kunden till dig, så att marginalen räknas från kundens första faktura.
Kunden börjar med en provperiod. Abonnemanget är avsiktligt inte en parameter: att välja det vore att bevilja kommersiella villkor. Svaret innehåller billing_setup_url, där kunden lägger till en betalningsmetod och konverterar; samma länk mejlas till kunden.
Nekas när affären inte är din, inte är godkänd eller vunnen, redan har en kund, eller när registrering är pausad för dig. 429 när det dagliga taket för att skapa kunder har nåtts.
GET /partner/statements
Dina avräkningar av marginal, nyaste period 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
}
]
}Avräkningar tas fram den 1:a i månaden för föregående kalendermånad.
Reglering sker i ordningen sent_at → partner_invoice_ref → paid_at. Ingenting betalas innan det finns en referens till en partnerfaktura, eftersom din faktura är det dokument som betalningen görs mot. Betalning sker 30 dagar efter att vi tagit emot den.
Ett genomarbetat flöde
Registrera från ett CRM i samma stund som en möjlighet skapas, och skapa sedan kunden när affären är vunnen:
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")"'Fel
Partner-endpoints använder plattformens gemensamma form application/problem+json. type-URL:en leder till en sida som beskriver det specifika felet; se API-fel.
Hela specifikationen
De här endpointerna ingår i det publicerade OpenAPI-dokumentet, under taggen Partner:
Specifikationen genereras från den körande tjänsten och gäller när den och den här sidan inte stämmer överens.