# Code examples for the Binadit API

- [Python](/developers/guides/code-examples-python?lang=en) · [Markdown](/developers/guides/code-examples-python.md?lang=en)
- [JavaScript / TypeScript](/developers/guides/code-examples-javascript?lang=en) · [Markdown](/developers/guides/code-examples-javascript.md?lang=en)
- [PHP](/developers/guides/code-examples-php?lang=en) · [Markdown](/developers/guides/code-examples-php.md?lang=en)
- [C#](/developers/guides/code-examples-csharp?lang=en) · [Markdown](/developers/guides/code-examples-csharp.md?lang=en)

## Getting started

Choose a language above. Each language guide includes a complete reusable HTTP client and the same important tasks. Source files are in `docs/examples/`; these are inspectable examples, not a separate official SDK package. Python and JavaScript use only their standard libraries; PHP requires cURL and C# uses .NET 10. Run JavaScript on the server, even when the rest of your application uses TypeScript.

Under **Account → API and integrations**, create a dedicated service connection with minimal scopes. Have your secret manager provide `BINADIT_CLIENT_ID` and `BINADIT_CLIENT_SECRET`. The client uses them to request one short-lived token through `POST /oauth/token` with `grant_type=client_credentials`. It does not save or print the secret or token. You can supply a personal token obtained by your own OAuth application through `BINADIT_ACCESS_TOKEN`. Do not extract personal tokens from the CLI's system keychain. For normal interactive use, run `binadit login`.

A service credential cannot replace recent human verification. Team invitations, webhook administration, enabling Send As and deleting rooms may require an authorised person with recent strong verification. Use your own approved personal OAuth connection or the existing CLI for those actions. The snippets do not bypass company policy or feature flags. Exchange and legacy balance top-ups remain outside this release.

The clients use only `https://my.binadit.com`, normal TLS verification, a timeout and no redirects or automatic retries. The fixed origin prevents a pagination or payment URL from unintentionally receiving a Bearer token. For your own test installation, deliberately change the origin constant; do not disable certificate verification. The examples read at most 8 MiB per response. Use a separate bounded streaming implementation for larger files.

## Supplying values and versions

All `example.test` addresses, documentation IP addresses, IDs and UUIDs are fictional. Replace them with resources read through the same company connection. `123` is the company, `7` a Workspace, `23/24` mailboxes, `8` a DNS zone and `12` a mail domain. An ID from another company grants no authority.

For pagination, follow `meta.next_cursor` even when permission filtering leaves the `data` array empty. The language guides show a bounded loop that also rejects repeated cursors. Do not follow an arbitrary next-page URL with your token.

The client returns `etag` and `data`; `data` is the complete JSON envelope, for example `result.data.data` in JavaScript. C# uses `ETag`, `Data` and, for a PDF, `Bytes`. Associate each ETag with its exact resource. For version-controlled mutations, the language guide identifies the GET to perform first. Put that reviewed ETag or explicit plan version in `BINADIT_RESOURCE_VERSION`. Do not invent a hash or use a version belonging to another object.

Before sending each reviewed write, durably preserve the original principal, method, URL, exact JSON, resource version and a random `Idempotency-Key` containing 16–128 safe characters. Supply that original key through `BINADIT_REQUEST_KEY`. A UUID is suitable: generate it once in your own durable request store, not again on every script launch. As a precaution, the example clients also require a key for read-oriented POST requests; that does not turn those endpoints into mutations.

Write examples use `dry_run: true` where relevant. A dry run does not save an executable order, grant or DNS plan. For real execution, review the preview first, read current versions and preserve the final JSON without `dry_run` together with its own key. Do not paste all examples into one sequence: each snippet is a separate task with its own input and key. A dry run of a sensitive action does not remove its human-verification requirement.

## Ordering, paying and cancelling

1. Read products, your company holder and capabilities. Ask the server for pricing; do not calculate your own selling price.
2. Review a quote preview. Then deliberately request a **saved** quote by omitting `dry_run`. Use the actual `data.id` and inspect that same quote with GET. A preview ID must not be treated as a real quote.
3. Preview confirmation through `orders` with `dry_run: true`. Only after approval, submit that same quote ID without `dry_run`, with a durably recorded key for that action. This can create an invoice and order. `orders:commit` and current ordering/spending privileges remain required.
4. The order may return `checkout_url`. Open it separately in a browser to confirm payment. Never send the Binadit token to the payment provider or fetch that URL with the example client.
5. Follow the **original** operation and read the domain again. `queued` or `awaiting_payment` does not prove registration. Check registrar status, DNS publication and subscription/automatic renewal separately.

Cancelling a domain disables automatic renewal; it does not immediately delete the registration. Afterwards, read `domains/{id}/subscription` again and check the recorded end date and billing status. Hosting and other services have their own cancellation intents; a domain and a hosting package are separate agreements.

## DNS and Workspace

DNS always goes through the Binadit API and the existing portal processing. Read records, create a plan, review the records and version, then apply the original plan separately. A template plan is also a proposal. Never use PowerDNS, NS1 or Cloudflare credentials directly in these clients. The record value field is `value`; CNAME targets follow the portal notation without a trailing dot. When updating or deleting, use record IDs actually returned by the read, not their positions in a newly sorted list.

Personal and shared mailboxes are paid products. Order a personal mailbox through a quote of kind `workspace_mailbox`; shared mailboxes follow their [own paid contract](/developers/guides/workspace-shared-mailboxes?lang=en). The delegate, calendar, mailing-list and room examples are free and require no product or invoice. `full` also grants Send on Behalf; `send_as` is a separate sensitive choice. When Send As is disabled while `full` remains, recipients see the delegate again. A list member is an existing typed ID such as `mailbox-24`, not an external address or nested list. Rooms have no login password; recurring meetings are checked only for their first occurrence. See the [complete mail contract](/developers/guides/workspace-mail-collaboration?lang=en).

## Errors, uncertain outcomes and progress

| Outcome | Action |
| --- | --- |
| 401 | Stop. First check whether a mutation could already have been sent. Request a new token for subsequent deliberate actions; do not automatically repeat a POST. |
| 403/404 | Check the company, scopes, resource restrictions and any human-verification requirement; do not work around the refusal with another company or administrator token. |
| 409 | Inspect the existing request or blocker. Do not replace it with a new key, quote or supplier request. |
| 412/428 | Read the correct resource again and review the changed situation; do not force a fresh ETag onto an old command. |
| 422 | Check fields, types and the OpenAPI contract. |
| 429 | Respect `Retry-After`. The helpers do not retry. Inspect an uncertain mutation before later reading again or requesting another action. |
| Timeout/5xx | The outcome may be unknown. Keep the original metadata and inspect the operation. A timeout does not prove that the server performed no action. |

`operation.request_status: succeeded` confirms the HTTP receipt, not necessarily the supplier action. Follow `operation.status`, including `queued`, `running`, `awaiting_payment`, `requires_approval`, `requires_reconciliation`, `failed` and `succeeded`. A held free mail task is `requires_reconciliation`, not a payment request. A failed colleague/DNS task is no reason to submit a second paid order.

Keep returned operation, plan and intent IDs with your original request. With `operations:read`, poll the same operation at a bounded rate, for example once every five seconds for at most two minutes per script execution. Stop at a review or payment status; do not force it through. After a new personal login, the original principal may have changed: use the [original receipt](/developers/guides/original-operations?lang=en) rather than automatically replaying under the new connection.

The clients deliberately report errors without the unfiltered response body. Do not log Bearer tokens, client secrets, one-time claims, personal details, mail content or PDFs. One-time password and key claims have a separate protocol and do not belong in a generic retry loop.

## Receiving webhooks

The language guides include an HMAC check for the **unmodified raw body** and the three headers `Binadit-Webhook-Id`, `Binadit-Webhook-Timestamp` and `Binadit-Webhook-Signature`. Obtain the expected key ID and secret from your own secret manager. Use TLS, check the time window, compare in constant time, then prevent replay with a unique delivery ID in durable storage. A valid signature alone does not prevent duplicate execution.

After verification, atomically record the delivery ID and a work item in one database transaction with a unique constraint. Return 2xx only after that commits. An already durably received, validly signed delivery gets 2xx again without repeating its work. Never process an invoice notification twice. When rotating keys, retain only deliberately approved key IDs belonging to your integration; never trust an unknown key_id from a header.

## Contract and verification

Examples are generated from the actual operation catalog and shared recipes. Routes, scopes, methods and JSON bodies are checked locally against [OpenAPI](/developers/openapi.json). The client code is separately executed against a local test receiver; this does not modify customer data or submit paid production actions. Development notes alongside the source record the PHP/Python/Node versions and .NET build check. This is not a claim that every illustrated paid action was ordered on production in all four languages.

The examples follow the current [release scope](/developers/guides/release-scope?lang=en), [API reference](/developers/api?lang=en#reference) and [agent guide](/developers/agents?lang=en). Standard-library references: [HttpClient redirects](https://learn.microsoft.com/dotnet/api/system.net.http.httpclienthandler.allowautoredirect), [Node fetch and timeouts](https://nodejs.org/api/globals.html).


## Tasks available in every language

| Task | Method | Scope |
| --- | --- | --- |
| Check the authenticated identity | `GET` | `account:read` |
| List accessible companies | `GET` | `account:read` |
| Check capabilities and scopes | `GET` | `account:read` |
| Read company details and their ETag | `GET` | `account:read` |
| Update company details | `PATCH` | `account:write` |
| Read products and prices | `GET` | `products:read` |
| List existing domain holders | `GET` | `holders:read` |
| Preview a default holder from company or personal details | `POST` | `holders:read, holders:write` |
| Create a company domain holder | `POST` | `holders:write` |
| Check domain availability | `POST` | `domains:read` |
| Quote a domain registration | `POST` | `orders:write` |
| Read a saved quote again | `GET` | `orders:read` |
| Preview an order and payment | `POST` | `orders:commit` |
| Read registration renewal and subscription status | `GET` | `domains:read` |
| Disable automatic domain renewal | `POST` | `domains:write` |
| List DNS zones | `GET` | `dns.zones:read` |
| Read current DNS records and version | `GET` | `dns.records:read` |
| List DNS templates | `GET` | `dns.templates:read` |
| Preview a DNS template | `POST` | `dns.templates:write` |
| Propose applying a template | `POST` | `dns.records:write` |
| Preview multiple DNS records in one plan | `POST` | `dns.records:write` |
| Apply a reviewed DNS plan | `POST` | `dns.records:write` |
| Follow the original operation | `GET` | `operations:read` |
| List invoices | `GET` | `billing.invoices:read` |
| Download an invoice PDF | `GET` | `billing.invoices:read` |
| List Workspaces | `GET` | `workspace:read` |
| List mailboxes | `GET` | `workspace.mailboxes:read` |
| Read delegates and user-configured shares | `GET` | `workspace.mailboxes:read` |
| Grant full mail access and calendar editing | `PUT` | `workspace.mailboxes:write` |
| Preview a free mailing list | `POST` | `workspace.mailboxes:write` |
| Add one mailing-list member | `PUT` | `workspace.mailboxes:write` |
| Remove one mailing-list member | `DELETE` | `workspace.mailboxes:write` |
| Preview a free room | `POST` | `workspace.mailboxes:write` |
| Add a room manager | `PUT` | `workspace.mailboxes:write` |
| Delete a room with personal verification | `DELETE` | `workspace.mailboxes:write` |
| List hosting subscriptions | `GET` | `hosting:read` |
| Preview hosting cancellation | `POST` | `hosting:write` |
| List spam filters | `GET` | `spam:read` |
| Invite a colleague with limited permissions | `POST` | `team:write` |
| Preview a paused webhook | `POST` | `webhooks:write` |
| Inspect webhook deliveries | `GET` | `webhooks:read` |
| Read the current product version | `GET` | `products:read` |
| Quote a paid personal mailbox | `POST` | `orders:write` |
| Preview a paid shared mailbox | `POST` | `workspace.mailboxes:write, orders:commit` |
| Enable Send As separately | `PUT` | `workspace.mailboxes:write` |
| Revoke mail and calendar access together | `DELETE` | `workspace.mailboxes:write` |
