From 37d82ec58e785ee0e6e7162e8e3dbac5d77b834f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 5 May 2026 09:08:06 +0200 Subject: [PATCH] Add SharingId for the X-Gandi-Sharing-Id header When the PAT user's default organization is not the one that owns the domain, list endpoints still find the domain (Gandi filters by all visible orgs) but per-record-set endpoints return 404. Sending X-Gandi-Sharing-Id with the org UUID makes Gandi resolve into that org's context and the lookups work. Setting Provider.SharingId injects the header on every API call. The default empty value preserves existing behaviour. --- README.md | 6 ++++++ client.go | 3 +++ provider.go | 1 + 3 files changed, 10 insertions(+) diff --git a/README.md b/README.md index fade626..41f105a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ This package only supports **API Key authentication**. Refer to the [Gandi's Pub Start by [retrieving your API key](https://account.gandi.net/) from the _Security_ section in Gandi account admin panel to be able to make authenticated requests to the API. +### Cross-organization access + +If the PAT user's default organization is not the one that owns the domain you want to manage, list endpoints (`/v5/domain/domains`, `/v5/livedns/domains`) will still find the domain because Gandi filters by all visible orgs, but per-record-set endpoints will return `404`. Setting `Provider.SharingId` to the UUID of the org that owns the domain causes the provider to send `X-Gandi-Sharing-Id: ` on every request, and the per-record-set endpoints then succeed. + +Note: the documented form of this hint is the [`sharing_id` query string parameter](https://api.gandi.net/docs/reference/#Sharing-ID), but in practice that form doesn't reach per-record-set endpoints. The `X-Gandi-Sharing-Id` header is empirically required there but isn't currently part of the public reference. + ## Technical limitations The [LiveDNS documentation](https://api.gandi.net/docs/livedns/) states that records with the same name and type are merged so that their `rrset_values` are grouped together. diff --git a/client.go b/client.go index 4314bed..0862bc8 100644 --- a/client.go +++ b/client.go @@ -161,6 +161,9 @@ func (p *Provider) getDomain(ctx context.Context, zone string) (gandiDomain, err func (p *Provider) doRequest(req *http.Request, result interface{}) (gandiStatus, error) { req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", p.BearerToken)) req.Header.Set("Accept", "application/json") + if p.SharingId != "" { + req.Header.Set("X-Gandi-Sharing-Id", p.SharingId) + } resp, err := http.DefaultClient.Do(req) diff --git a/provider.go b/provider.go index a5e7ece..712f4b1 100644 --- a/provider.go +++ b/provider.go @@ -12,6 +12,7 @@ import ( // Provider implements the libdns interfaces for Gandi. type Provider struct { BearerToken string `json:"bearer_token,omitempty"` + SharingId string `json:"sharing_id,omitempty"` domains map[string]gandiDomain mutex sync.Mutex