# Binadit API with C#

[All languages and safety rules](/developers/guides/code-examples?lang=en) · [API reference](/developers/api?lang=en#reference)

.NET 10. Create a project with dotnet new console and add Binadit.cs. Use the calls below from your async Program.cs.

## 1. Reusable client

Save this as `Binadit.cs`. The client follows no redirects and does not retry requests. Supply secrets in the environment through your secret manager; nothing is automatically saved or logged.

```csharp
// .NET 10, standard library only. Reuse one client; no automatic token refresh or retry.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Security.Cryptography;

public sealed record BinaditReply(string? ETag, JsonNode? Data, byte[] Bytes);

public sealed class BinaditExample : IDisposable
{
    private readonly HttpClient http = new(new HttpClientHandler { AllowAutoRedirect = false, UseCookies = false })
    { Timeout = TimeSpan.FromSeconds(30), MaxResponseContentBufferSize = 8 * 1024 * 1024 };
    private const string Origin = "https://my.binadit.com";

    private async Task<(HttpResponseMessage Response, byte[] Bytes)> Send(string method, string path, string? body, string contentType, string? token = null, string? version = null, string? key = null, bool binary = false)
    {
        if ((!path.StartsWith("/api/v1/", StringComparison.Ordinal) && path != "/oauth/token") || path.Contains('\\') || path.Contains('#'))
            throw new ArgumentException("Use a relative Binadit API path");
        using var request = new HttpRequestMessage(new HttpMethod(method), Origin + path);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(binary ? "application/pdf" : "application/json"));
        if (token is not null) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        if (version is not null) request.Headers.Add("If-Match", version);
        if (key is not null) request.Headers.Add("Idempotency-Key", key);
        if (body is not null) request.Content = new StringContent(body, Encoding.UTF8, contentType);
        HttpResponseMessage response;
        try { response = await http.SendAsync(request); }
        catch (HttpRequestException) { throw new InvalidOperationException("Transport outcome unknown; preserve the original request"); }
        catch (TaskCanceledException) { throw new InvalidOperationException("Transport outcome unknown; preserve the original request"); }
        if (!response.IsSuccessStatusCode) { int code = (int)response.StatusCode; response.Dispose(); throw new InvalidOperationException($"API HTTP {code}; inspect the original operation"); }
        return (response, await response.Content.ReadAsByteArrayAsync());
    }

    public async Task<string> AccessToken()
    {
        if (Environment.GetEnvironmentVariable("BINADIT_ACCESS_TOKEN") is { Length: > 0 } token) return token;
        string Required(string name) => Environment.GetEnvironmentVariable(name) ?? throw new InvalidOperationException("Supply service credentials from your secret manager");
        using var form = new FormUrlEncodedContent(new Dictionary<string, string> {
            ["grant_type"] = "client_credentials", ["client_id"] = Required("BINADIT_CLIENT_ID"), ["client_secret"] = Required("BINADIT_CLIENT_SECRET") });
        var (response, bytes) = await Send("POST", "/oauth/token", await form.ReadAsStringAsync(), "application/x-www-form-urlencoded");
        using (response) return JsonNode.Parse(bytes)?["access_token"]?.GetValue<string>() is { Length: > 0 } value ? value : throw new InvalidOperationException("Token response is invalid");
    }

    public async Task<BinaditReply> Api(string token, string method, string path, JsonNode? body = null, string? version = null, string? key = null, bool binary = false)
    {
        if (!path.StartsWith("/api/v1/", StringComparison.Ordinal) || !new[] { "GET", "POST", "PUT", "PATCH", "DELETE" }.Contains(method)) throw new ArgumentException("Use a supported API method and path");
        if (method != "GET" && !Regex.IsMatch(key ?? "", @"\A[A-Za-z0-9_.:-]{16,128}\z")) throw new ArgumentException("Supply the persisted original Idempotency-Key");
        var (response, bytes) = await Send(method, path, body?.ToJsonString(), "application/json", token, version, key, binary);
        using (response)
        {
            if (binary && response.Content.Headers.ContentType?.MediaType != "application/pdf") throw new InvalidOperationException("Expected an invoice PDF");
            return new(response.Headers.ETag?.ToString(), binary || bytes.Length == 0 ? null : JsonNode.Parse(bytes), binary ? bytes : []);
        }
    }
    public void Dispose() => http.Dispose();

    public static bool VerifyWebhook(byte[] raw, string deliveryId, string timestamp, string signature, string expectedKeyId, string secret, long? now = null)
    {
        const string uuid = @"\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z";
        if (raw.Length > 1024 * 1024 || secret.Length < 32 || !Regex.IsMatch(deliveryId, uuid) || !Regex.IsMatch(expectedKeyId, uuid)
            || !Regex.IsMatch(timestamp, @"\A[1-9][0-9]{8,10}\z") || Math.Abs((now ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds()) - long.Parse(timestamp)) > 300) return false;
        byte[] input = Encoding.UTF8.GetBytes(timestamp + "." + deliveryId + ".").Concat(raw).ToArray();
        string digest = Convert.ToHexString(HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), input)).ToLowerInvariant();
        return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes("v1=" + digest + ",key_id=" + expectedKeyId), Encoding.UTF8.GetBytes(signature));
    }
}
```

## 2. Once for this workflow

```csharp
using System.Text.Json.Nodes;
using var client = new BinaditExample();
var token = await client.AccessToken();
```

Request a token once for this short workflow. A service credential is not human step-up. Stop on 401 and inspect any mutation that might already have been sent. Each code block below is a separate example: do not paste them all into a single sequence. All IDs and example.test addresses are fictional. Write examples with possible effects use dry_run.

## Check the authenticated identity

`GET /api/v1/me` · scope `account:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/me");
```

## List accessible companies

`GET /api/v1/accounts` · scope `account:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts");
```

## Check capabilities and scopes

`GET /api/v1/accounts/123/capabilities` · scope `account:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/capabilities");
```

## Read company details and their ETag

`GET /api/v1/accounts/123/profile` · scope `account:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/profile");
```

## Update company details

`PATCH /api/v1/accounts/123/profile` · scope `account:write`

First read **GET profile** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "PATCH", "/api/v1/accounts/123/profile", JsonNode.Parse("""{"city": "Utrecht", "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Read products and prices

`GET /api/v1/accounts/123/products` · scope `products:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/products");
```

## List existing domain holders

`GET /api/v1/accounts/123/holders` · scope `holders:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/holders");
```

## Preview a default holder from company or personal details

`POST /api/v1/accounts/123/holders/default` · scope `holders:read, holders:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/holders/default", JsonNode.Parse("""{"dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Create a company domain holder

`POST /api/v1/accounts/123/holders` · scope `holders:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/holders", JsonNode.Parse("""{"initials": "A", "firstname": "Ada", "lastname": "Example", "gender": "F", "street": "Voorbeeldstraat", "number": "1", "zipcode": "1234 AB", "city": "Utrecht", "country": "NL", "email": "ada@example.test", "tel1": "+31", "tel3": "612345678", "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Check domain availability

`POST /api/v1/accounts/123/domains/check` · scope `domains:read`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/domains/check", JsonNode.Parse("""{"domains": ["example.test"]}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Quote a domain registration

`POST /api/v1/accounts/123/order-quotes` · scope `orders:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/order-quotes", JsonNode.Parse("""{"kind": "domain_register", "input": {"domain": "example.test", "product_id": 9, "holder_id": 17}, "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Read a saved quote again

`GET /api/v1/accounts/123/order-quotes/11111111-1111-4111-8111-111111111111` · scope `orders:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/order-quotes/11111111-1111-4111-8111-111111111111");
```

## Preview an order and payment

`POST /api/v1/accounts/123/orders` · scope `orders:commit`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/orders", JsonNode.Parse("""{"quote_id": "11111111-1111-4111-8111-111111111111", "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Read registration renewal and subscription status

`GET /api/v1/accounts/123/domains/31/subscription` · scope `domains:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/domains/31/subscription");
```

## Disable automatic domain renewal

`POST /api/v1/accounts/123/domains/31/actions/cancel` · scope `domains:write`

First read **GET domains/31** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/domains/31/actions/cancel", JsonNode.Parse("""{"confirm_domain": "example.test", "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## List DNS zones

`GET /api/v1/accounts/123/dns/zones` · scope `dns.zones:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/dns/zones");
```

## Read current DNS records and version

`GET /api/v1/accounts/123/dns/zones/8/records` · scope `dns.records:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/dns/zones/8/records");
```

## List DNS templates

`GET /api/v1/accounts/123/dns/templates` · scope `dns.templates:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/dns/templates");
```

## Preview a DNS template

`POST /api/v1/accounts/123/dns/templates` · scope `dns.templates:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/dns/templates", JsonNode.Parse("""{"name": "Websitevoorbeeld", "replace": "none", "records": [{"name": "@", "type": "A", "value": "192.0.2.50", "ttl": 300}, {"name": "www", "type": "CNAME", "value": "example.test", "ttl": 300}], "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Propose applying a template

`POST /api/v1/accounts/123/dns/zones/8/template-plans` · scope `dns.records:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/dns/zones/8/template-plans", JsonNode.Parse("""{"template_id": 11, "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Preview multiple DNS records in one plan

`POST /api/v1/accounts/123/dns/change-plans` · scope `dns.records:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/dns/change-plans", JsonNode.Parse("""{"zone": "example.test", "changes": [{"action": "create", "name": "proef", "type": "A", "value": "192.0.2.50", "ttl": 300}, {"action": "create", "name": "_verify", "type": "TXT", "value": "binadit-example", "ttl": 300}], "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Apply a reviewed DNS plan

`POST /api/v1/accounts/123/dns/change-plans/11111111-1111-4111-8111-111111111111/apply` · scope `dns.records:write`

First read **the version of the original reviewed DNS plan** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/dns/change-plans/11111111-1111-4111-8111-111111111111/apply", JsonNode.Parse("""{"dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Follow the original operation

`GET /api/v1/accounts/123/operations/22222222-2222-4222-8222-222222222222` · scope `operations:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/operations/22222222-2222-4222-8222-222222222222");
```

## List invoices

`GET /api/v1/accounts/123/billing/invoices` · scope `billing.invoices:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/billing/invoices");
```

## Download an invoice PDF

`GET /api/v1/accounts/123/billing/invoices/45/pdf` · scope `billing.invoices:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/billing/invoices/45/pdf", binary: true);
```

`result.Bytes` contains only PDF bytes. Use the separate private-file examples below; do not print PDF content or personal data.

## List Workspaces

`GET /api/v1/accounts/123/workspaces` · scope `workspace:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/workspaces");
```

## List mailboxes

`GET /api/v1/accounts/123/workspaces/7/mailboxes` · scope `workspace.mailboxes:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/workspaces/7/mailboxes");
```

## Read delegates and user-configured shares

`GET /api/v1/accounts/123/workspaces/7/mailboxes/23/delegates` · scope `workspace.mailboxes:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/workspaces/7/mailboxes/23/delegates");
```

## Grant full mail access and calendar editing

`PUT /api/v1/accounts/123/workspaces/7/mailboxes/23/delegates/24` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/mailboxes/23** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "PUT", "/api/v1/accounts/123/workspaces/7/mailboxes/23/delegates/24", JsonNode.Parse("""{"level": "full", "send_as": false, "calendar_level": "write", "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Preview a free mailing list

`POST /api/v1/accounts/123/workspaces/7/mailing-lists` · scope `workspace.mailboxes:write`

First read **GET workspaces/7** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/workspaces/7/mailing-lists", JsonNode.Parse("""{"domain_id": 12, "local_part": "team", "display_name": "Team", "members": [{"kind": "mailbox", "id": 23}, {"kind": "mailbox", "id": 24}], "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Add one mailing-list member

`PUT /api/v1/accounts/123/workspaces/7/mailing-lists/10/members/mailbox-24` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/mailing-lists/10** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "PUT", "/api/v1/accounts/123/workspaces/7/mailing-lists/10/members/mailbox-24", JsonNode.Parse("""{"dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Remove one mailing-list member

`DELETE /api/v1/accounts/123/workspaces/7/mailing-lists/10/members/mailbox-24` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/mailing-lists/10** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "DELETE", "/api/v1/accounts/123/workspaces/7/mailing-lists/10/members/mailbox-24", JsonNode.Parse("""{"dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Preview a free room

`POST /api/v1/accounts/123/workspaces/7/rooms` · scope `workspace.mailboxes:write`

First read **GET workspaces/7** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/workspaces/7/rooms", JsonNode.Parse("""{"domain_id": 12, "local_part": "vergaderzaal", "display_name": "Vergaderzaal 1", "kind": "room", "capacity": 8, "location": "Eerste verdieping", "manager_ids": [23], "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Add a room manager

`PUT /api/v1/accounts/123/workspaces/7/rooms/11/managers/24` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/rooms/11** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "PUT", "/api/v1/accounts/123/workspaces/7/rooms/11/managers/24", JsonNode.Parse("""{"dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Delete a room with personal verification

`DELETE /api/v1/accounts/123/workspaces/7/rooms/11` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/rooms/11** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

Also requires recent strong human verification, current privileges and enabled sensitive operations. A service credential cannot supply that verification.

```csharp
var result = await client.Api(token, "DELETE", "/api/v1/accounts/123/workspaces/7/rooms/11", JsonNode.Parse("""{"confirm_address": "vergaderzaal@example.test", "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## List hosting subscriptions

`GET /api/v1/accounts/123/hosting/subscriptions` · scope `hosting:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/hosting/subscriptions");
```

## Preview hosting cancellation

`POST /api/v1/accounts/123/hosting/subscriptions/41/actions/cancel` · scope `hosting:write`

First read **GET hosting/subscriptions/41** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/hosting/subscriptions/41/actions/cancel", JsonNode.Parse("""{"intent_id": "33333333-3333-4333-8333-333333333333", "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## List spam filters

`GET /api/v1/accounts/123/spam/filters` · scope `spam:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/spam/filters");
```

## Invite a colleague with limited permissions

`POST /api/v1/accounts/123/team/invitations` · scope `team:write`

First read **GET profile** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

Also requires recent strong human verification, current privileges and enabled sensitive operations. A service credential cannot supply that verification.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/team/invitations", JsonNode.Parse("""{"email": "collega@example.test", "role": "member", "permissions": [{"module": "module_dns", "level": "read"}], "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Preview a paused webhook

`POST /api/v1/accounts/123/webhooks` · scope `webhooks:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

Also requires recent strong human verification, current privileges and enabled sensitive operations. A service credential cannot supply that verification.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/webhooks", JsonNode.Parse("""{"name": "Orderstatus", "url": "https://receiver.example.test/binadit", "integration_id": "44444444-4444-4444-8444-444444444444", "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Inspect webhook deliveries

`GET /api/v1/accounts/123/webhooks/55555555-5555-4555-8555-555555555555/deliveries` · scope `webhooks:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/webhooks/55555555-5555-4555-8555-555555555555/deliveries");
```

## Read the current product version

`GET /api/v1/accounts/123/products/9` · scope `products:read`

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/products/9");
```

## Quote a paid personal mailbox

`POST /api/v1/accounts/123/order-quotes` · scope `orders:write`

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "POST", "/api/v1/accounts/123/order-quotes", JsonNode.Parse("""{"kind": "workspace_mailbox", "input": {"workspace_id": 7, "product_id": 9, "domain_id": 12, "local_part": "ada", "display_name": "Ada", "drive": false}, "dry_run": true}"""), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Preview a paid shared mailbox

`POST /api/v1/accounts/123/workspaces/7/shared-mailboxes` · scope `workspace.mailboxes:write, orders:commit`

First read **GET workspaces/7** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

Also read `GET products/9` and save its unquoted `data.version` as `BINADIT_PRODUCT_VERSION`. This paid shared mailbox requires `orders:commit` and current ordering/spending privileges in addition to mail access. A product version is not a Workspace version.

```csharp
var payload = JsonNode.Parse("""{"domain_id": 12, "product_id": 9, "local_part": "support", "display_name": "Support", "member_ids": [23, 24], "dry_run": true}""")!;
payload["product_version"] = Environment.GetEnvironmentVariable("BINADIT_PRODUCT_VERSION") ?? throw new InvalidOperationException("Read the product version first");
var result = await client.Api(token, "POST", "/api/v1/accounts/123/workspaces/7/shared-mailboxes", payload, version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Enable Send As separately

`PUT /api/v1/accounts/123/workspaces/7/mailboxes/23/delegates/24` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/mailboxes/23** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

Enabling Send As requires recent strong personal verification and full mail access. A service credential with only the stated scope is insufficient.

```csharp
var result = await client.Api(token, "PUT", "/api/v1/accounts/123/workspaces/7/mailboxes/23/delegates/24", JsonNode.Parse("""{"level": "full", "send_as": true, "calendar_level": "write", "dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Revoke mail and calendar access together

`DELETE /api/v1/accounts/123/workspaces/7/mailboxes/23/delegates/24` · scope `workspace.mailboxes:write`

First read **GET workspaces/7/mailboxes/23** and preserve its HTTP ETag (or explicitly documented plan version) as `BINADIT_RESOURCE_VERSION`. Versions belonging to different resources are not interchangeable.

For exactly this reviewed request, use a previously durably recorded `BINADIT_REQUEST_KEY` containing 16–128 safe characters. Reuse it only for the same method, URL and JSON, never for the next step.

```csharp
var result = await client.Api(token, "DELETE", "/api/v1/accounts/123/workspaces/7/mailboxes/23/delegates/24", JsonNode.Parse("""{"dry_run": true}"""), version: Environment.GetEnvironmentVariable("BINADIT_RESOURCE_VERSION") ?? throw new InvalidOperationException("Read the current ETag first"), key: Environment.GetEnvironmentVariable("BINADIT_REQUEST_KEY"));
```

## Further workflows

### Paginate without skipping resources

```csharp
string? cursor = null;
var seen = new HashSet<string>();
for (int page = 0; page < 20; page++)
{
    var path = "/api/v1/accounts/123/domains?limit=25";
    if (cursor is not null) path += "&cursor=" + Uri.EscapeDataString(cursor);
    var result = await client.Api(token, "GET", path);
    foreach (var domain in result.Data!["data"]!.AsArray()) Console.WriteLine(domain!["id"]);
    cursor = result.Data["meta"]?["next_cursor"]?.GetValue<string>();
    if (cursor is null) break;
    if (!seen.Add(cursor)) throw new InvalidOperationException("Repeated pagination cursor");
    if (page == 19) throw new InvalidOperationException("Page limit reached; explicitly continue from the saved cursor");
}
```

An empty array can still have a next cursor. Use only the trusted resource path belonging to this API, not an external next-page URL.

### Save a PDF in a new private file

```csharp
var result = await client.Api(token, "GET", "/api/v1/accounts/123/billing/invoices/45/pdf", binary: true);
var options = new FileStreamOptions { Mode = FileMode.CreateNew, Access = FileAccess.Write, Share = FileShare.None };
if (!OperatingSystem.IsWindows()) options.UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite;
await using var output = new FileStream("factuur-45.pdf", options);
await output.WriteAsync(result.Bytes);
```

On Windows, the file inherits its directory ACL: choose a directory accessible only to the intended user. Do not log PDF bytes.

### Verify a webhook signature

```csharp
// Obtain these raw bytes and headers from your server framework before JSON parsing.
bool valid = BinaditExample.VerifyWebhook(rawBody, deliveryIdHeader, timestampHeader, signatureHeader,
    Environment.GetEnvironmentVariable("BINADIT_WEBHOOK_KEY_ID") ?? "",
    Environment.GetEnvironmentVariable("BINADIT_WEBHOOK_SECRET") ?? "");
if (!valid) throw new InvalidOperationException("Reject this webhook");
// Next: atomically insert the delivery ID (UNIQUE) and enqueue work in your DB.
```

The complete helper above uses `HMACSHA256` and `CryptographicOperations.FixedTimeEquals`. Configure a body limit and validate missing and duplicate headers in your framework. Record the delivery ID and work item in one transaction before returning 2xx. A valid message already recorded durably receives 2xx again without duplicate execution. This helper is not a complete webhook server and does not create durable replay storage.
