diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4d042db9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Store and check out text files with LF line endings, whatever the platform. +* text=auto eol=lf diff --git a/plugins/alpaca/.claude-plugin/plugin.json b/plugins/alpaca/.claude-plugin/plugin.json index 001e5427..1681da8f 100644 --- a/plugins/alpaca/.claude-plugin/plugin.json +++ b/plugins/alpaca/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "alpaca", "displayName": "Alpaca", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Trader API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/alpaca/.cursor-plugin/plugin.json b/plugins/alpaca/.cursor-plugin/plugin.json index 001e5427..1681da8f 100644 --- a/plugins/alpaca/.cursor-plugin/plugin.json +++ b/plugins/alpaca/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "alpaca", "displayName": "Alpaca", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Trader API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/alpaca/README.md b/plugins/alpaca/README.md index 1deb46ec..d3603f77 100644 --- a/plugins/alpaca/README.md +++ b/plugins/alpaca/README.md @@ -1,13 +1,13 @@ -# Trader API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Trader API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-alpaca`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-alpaca`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-alpaca`, `typescript-models`, `typescript-testing` | +# Trader API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Trader API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-alpaca`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-alpaca`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-alpaca`, `typescript-models`, `typescript-testing` | diff --git a/plugins/alpaca/plugin.json b/plugins/alpaca/plugin.json index 001e5427..1681da8f 100644 --- a/plugins/alpaca/plugin.json +++ b/plugins/alpaca/plugin.json @@ -1,7 +1,7 @@ { "name": "alpaca", "displayName": "Alpaca", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Trader API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/alpaca/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/alpaca/skills/dotnet/dotnet-getting-started/SKILL.md index fe69d6a2..8a5b5e27 100644 --- a/plugins/alpaca/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/alpaca/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Alpaca .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-alpaca is the workflow that uses it." ---- - -# Getting started with the Alpaca .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Alpaca SDK fact. `dotnet-integrate-alpaca` is the workflow that drives it — you collect the contracts for the work in scope into `alpaca-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Alpaca | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/alpaca-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `Alpaca` (the `using` namespace) | -| Client class | `AlpacaClient` | -| Options class | `AlpacaClientOptions` | -| Auth | **API key** in the `APCA-API-KEY-ID` header — set `ApiKey`
**API key** in the `APCA-API-SECRET-KEY` header — set `ApiSecret` | -| Environments | `ServerEnvironment.Paper` *(default)* → `https://paper-api.alpaca.markets`
`ServerEnvironment.Live` → `https://api.alpaca.markets` | -| Log environment variable | `ALPACACLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-alpaca` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using Alpaca.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `AlpacaClient` | `Alpaca` | -| `AlpacaClientOptions` | `Alpaca` | -| `ServerEnvironment` | `Alpaca.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Alpaca, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/Alpaca/Models/`, `src/Alpaca/Errors/` and `src/Alpaca/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/Alpaca/Models/`, enums `src/Alpaca/Models/Enums/`, typed errors `src/Alpaca/Errors/`, request records `src/Alpaca/Requests/`, or `src/Alpaca/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `alpaca-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/alpaca-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/alpaca-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/alpaca-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/alpaca-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\alpaca-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/alpaca-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `alpaca-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/Alpaca/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/Alpaca/Models/` (+ `src/Alpaca/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/Alpaca/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/Alpaca/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/Alpaca/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/Alpaca/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/Alpaca/Servers/`, `src/Alpaca/AlpacaClient.cs`, `src/Alpaca/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Alpaca may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `ALPACACLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/Alpaca/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-alpaca`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new AlpacaClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `AlpacaClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Alpaca .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-alpaca is the workflow that uses it." +--- + +# Getting started with the Alpaca .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Alpaca SDK fact. `dotnet-integrate-alpaca` is the workflow that drives it — you collect the contracts for the work in scope into `alpaca-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Alpaca | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/alpaca-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `Alpaca` (the `using` namespace) | +| Client class | `AlpacaClient` | +| Options class | `AlpacaClientOptions` | +| Auth | **API key** in the `APCA-API-KEY-ID` header — set `ApiKey`
**API key** in the `APCA-API-SECRET-KEY` header — set `ApiSecret` | +| Environments | `ServerEnvironment.Paper` *(default)* → `https://paper-api.alpaca.markets`
`ServerEnvironment.Live` → `https://api.alpaca.markets` | +| Log environment variable | `ALPACACLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-alpaca` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using Alpaca.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `AlpacaClient` | `Alpaca` | +| `AlpacaClientOptions` | `Alpaca` | +| `ServerEnvironment` | `Alpaca.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Alpaca, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/Alpaca/Models/`, `src/Alpaca/Errors/` and `src/Alpaca/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/Alpaca/Models/`, enums `src/Alpaca/Models/Enums/`, typed errors `src/Alpaca/Errors/`, request records `src/Alpaca/Requests/`, or `src/Alpaca/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `alpaca-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/alpaca-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/alpaca-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/alpaca-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/alpaca-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\alpaca-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/alpaca-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `alpaca-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/Alpaca/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/Alpaca/Models/` (+ `src/Alpaca/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/Alpaca/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/Alpaca/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/Alpaca/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/Alpaca/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/Alpaca/Servers/`, `src/Alpaca/AlpacaClient.cs`, `src/Alpaca/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Alpaca may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `ALPACACLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/Alpaca/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-alpaca`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new AlpacaClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `AlpacaClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/alpaca/skills/dotnet/dotnet-integrate-alpaca/SKILL.md b/plugins/alpaca/skills/dotnet/dotnet-integrate-alpaca/SKILL.md index c78434e7..3faf1676 100644 --- a/plugins/alpaca/skills/dotnet/dotnet-integrate-alpaca/SKILL.md +++ b/plugins/alpaca/skills/dotnet/dotnet-integrate-alpaca/SKILL.md @@ -1,173 +1,173 @@ ---- -name: "dotnet-integrate-alpaca" -description: "MANDATORY FIRST STEP for Alpaca .NET SDK work in a C#/.NET project — load this BEFORE opening the SDK map or touching any project file; .NET/C# SDK ONLY, never load it for any other language. Applies when asked to integrate Alpaca in C# — AccountActivities, AccountConfigurationsApi, Accounts, CalendarApi, ClockApi, Orders, PortfolioHistoryApi, Positions, Watchlists — or when a Alpaca .NET SDK call errors or behaves unexpectedly. It carries four binding rules stated NOWHERE else — (1) for implementation work, the plan file alpaca-plan.md is written at the project repo root BEFORE any project file is created or edited; (2) dotnet-getting-started and the SDK map it carries are loaded and used for every contract fact, never memory; (3) every dotnet-* skill the contract sheet's REQUIRED READING names is loaded before implementation starts; (4) every sheet row carries one of three labels — a map page, UNVERIFIED, or YOUR CALL — not in the map. Skip it and SDK facts get written from stale memory." ---- - -# Alpaca .NET SDK — Workflow (map + skills) - -You are the Alpaca .NET SDK specialist for this task — one agent for every SDK need: planning, answering contract questions, implementing, and fixing SDK errors. Your scope is **C#/.NET only**: if a request concerns any other language or SDK, say so and stop. Your source of truth is the **SDK map that ships inside the SDK source repo** (`sdk-map.md` plus its `map/operations/` pages; model, enum and error shapes live in the source files those pages name), obtained per `dotnet-getting-started`, plus the companion `dotnet-*` skills for usage traps. Your training data on this SDK is stale — every fact you emit must come from a map page or a map-named source file you actually read this session, in that same clone. You never guess, and you never open the SDK's `api-reference.md`. You do all of it yourself — ground, plan, implement, fix — there is no helper agent to delegate to. - -**Scope guard:** the APIMatic-generated Alpaca **.NET SDK** (root namespace `Alpaca`) in **C#/.NET projects only**. Unrelated API, or any language other than C#/.NET — do nothing; this skill does not apply. - -## Grounding rules - -- **Map for contracts; the map-named file for shapes.** Follow `dotnet-getting-started`'s *SDK map* and *SDK source* sections in full — they define how to obtain the source (the map lives inside it), that reading a model/enum/error shape means opening the one declaring file the map names (routine, scoped), and why locating anything by grep/glob/`find` over the tree is a defect rather than a shortcut. The clone stays in the system temp directory, and `alpaca-plan.md` never carries its path — the plan must stay portable. -- **Never grep, glob or `find` the SDK tree to locate something** — the map is the locator, and a type name is its path. Every lookup starts at the map index and ends on the one page (or the one source file) it names. -- **Your contract sheet never leaves a contract fact open for "later."** The map pages and the files they name answer nearly everything — resolve them during planning, not mid-implementation. For the rare in-scope fact even the source cannot settle: if only live traffic could confirm it, convert it into a concrete defensive-coding directive on the sheet and label the uncertainty `UNVERIFIED`. Never leave a row marked for a later source lookup: an open row is how you end up opening source mid-implementation. -- **When you judge how far a contract can be trusted**, the trust judgment may cite ONLY evidence visible in the map or SDK source (e.g. two generated definitions that disagree, a suspicious shared model) — never training-data memory of this API, and never claims about what the live wire "usually" sends. -- **The sheet records Alpaca's call surface; the application's design is decided at implementation time.** Its persistence, its concurrency rules, and the request contract its own callers must satisfy are not SDK facts. Where an SDK fact forces an application decision, record the fact and its consequence on the sheet, mark the decision `YOUR CALL — not in the map`, and decide it at implementation time against the task. -- **Name only what you have read.** Never name a claim, header or route of the application as though you knew it exists — unless you have read that code. Name configuration by its binding key, never by a raw environment variable, and give the default the map documents where there is one: a setting you invent a name for is a setting no deployment will supply. - -## Workflow - -**If the user opens with a reported SDK error or unexpected Alpaca behaviour** (not new feature work), go straight to *Step 4 — Fixing SDK errors* below — do not run the plan-first flow for a bug report. Otherwise, for implementation work: - -### Step 1 — Plan first: write `alpaca-plan.md` (always, for any implementation work) - -Your FIRST deliverable is `alpaca-plan.md` at `/alpaca-plan.md` — that exact path, always; never pick another location. It carries the plan and the contract sheet in the format below, and it is the only project-repo file you write before the HARD GATE further down. - -#### How to ground (map-first, one pass) - -1. Load `dotnet-getting-started`; open `sdk-map.md` (the index). -2. From the index, open the **operations pages** for every controller in scope — take signatures (the request record each operation takes, and its required members), return types, error case (A: typed `ApiException<{Operation}Error>` with its `TryGet…` accessors and payload type / B: `ApiException`), and pagination — remembering that a block silent on a defaults-table point means the default applies. Where an operation's *semantics* decide what you must pass, that prose is the method's XML `` in the `Api/` file the page names, and the per-member docs on its request record: read it there. -3. Open, scoped, the **declaring source file** for every request/response model you will reference — the operation's **Type sources** table names it — and take field names WITH their `[JsonPropertyName]` wire names, `required` flags, nullability, and the envelope shape. Enum value lists and union accessors come from their declaring files the same way. -4. Identify which companion skill governs each step in scope. For each, write the trap note as a **named hazard plus a `MUST load` pointer** — *not* as the resolved answer. Naming the hazard ("what `Timeout` actually bounds") is right; resolving it inline ("`Timeout` is per-attempt") is wrong — a resolved trap gives you no reason to load the skill, and the skill carries the parts a one-line note cannot. **Never restate a companion skill's default or semantics in a trap note — not even when you believe it correct.** Write the *consequence*, not the answer. Contract *facts* are the opposite: those you resolve fully and inline. -5. Collect everything in ONE pass — the whole point is that you never have to rediscover a contract mid-coding. - -#### Prerequisites you can run alongside - -These need no SDK knowledge and touch no project file, so do them before or alongside the map work: - -- reaching the SDK source (per `dotnet-getting-started` — the map lives inside it, so this comes before any map work); -- the repo survey (read-only exploration of conventions and layering) — record each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; -- `dotnet restore` and a baseline `dotnet build` / `dotnet test` of the UNTOUCHED solution, so later failures are attributable to your changes; -- locating the SDK reference and its version in the project; -- credentials/environment verification (per the task's secret-handling rules); -- setting up your task tracking. - -Never use this prerequisite work as a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. - -#### HARD GATE - -**No project-file creation or edits until `alpaca-plan.md` exists at that path with every section below filled in.** Starting to code before the sheet exists defeats the plan-first design: the sheet is what keeps SDK facts out of your memory and in the map. The gate bars coding before the sheet exists; it does not bar the read-only prerequisite work above. - -#### The `alpaca-plan.md` format (keep it tight — tables, not prose) - -1. **Scope & sequence** — the implementation steps in order, each naming the operations it uses. A capability the map lacks is a Blocker (§6), never a data path you invent to replace it. -2. **CONTRACT SHEET** — open the section with two literal warning lines: that signatures are generated code, verbatim — each operation that takes input takes ONE request record as its first parameter (an operation with no inputs takes none), built with an object initializer whose property names are the record's own, never flat arguments; and that every SDK type is written fully-qualified with the namespace its source path implies, taken from the path the map gives for THAT type, never from where a neighbouring type sits. Then one table row per operation: controller property · method signature · request record + its members (`Name: type, required?`) · body model + its fields (`Name (wire_name): type, required?`) · response envelope + the inner fields the integration reads · error case A/B + accessors + payload type · pagination · **source** (§7). Below it: the enum value tables actually needed, and the client construction/auth/server-node facts. ⚠ A request model may mark nothing required, and then `required?` selects nothing for you — carry the optional fields the endpoint's own prose ties to whether the call is accepted, and say which doc-named fields you left out. - -The CONTRACT SHEET also carries a **CROSS-OPERATION INVARIANTS** table, directly below its per-operation rows: the rules no single row can state, because they relate two operations. One row per value the task lets a caller supply where a *different* operation defines the legal set for it (identifiers, handles, codes, references); `none` if the task has no such value — never leave the table out. - -| Invariant | Operations | Enforced where | -| --- | --- | --- | -| a value accepted by X must be one Y returned | `{WriteOperation}` ← `{ListOperation}` | before `{WriteOperation}` is called | - -⚠⚠ The map never states these rules; derive them from the task. - -⚠⚠ **Operations** names the operation that *defines* the legal set: the list or search call that returns what this application offers, or the earlier call whose result this application stored. A lookup that confirms a single value exists is not an answer: it proves the value exists at the provider, not that it is one this application offers. - -**Enforced where** names the member that checks, before the write, that the caller's value is in that set. If the application never reads that set, say so there and add the read — the check is part of the work. `YOUR CALL — not in the map` is not a legal entry in any cell of this table. - -3. **Trap notes** — one line per hazard, attached to the step where it bites, each ending in an inline **`MUST load `** pointer. Name the hazard and its consequence; do not resolve it. ⚠⚠ **"Do not resolve it" is the load-bearing half of this rule.** A trap note that answers its own question hands you a usable one-liner — and holding one, you do not open the skill. You implement from the sentence, and everything the skill carries beyond it never reaches the code. So: state the hazard, state what it costs, hand over the skill, and stop. No fix, no snippet, not even a partial answer. A trap note from which you could write correct code without loading the named skill is a defect. -4. **REQUIRED READING** — close the sheet with the de-duplicated list of every `dotnet-*` skill named above, one line each: skill · the step it governs. Write each name so the copy is unambiguous — every APIMatic .NET plugin ships these same skill names, and a bare name gives you no way to tell which one you loaded. State that these are to be loaded **before implementation starts**, and that the sheet deliberately does not carry their contents. This block is mandatory even when the trap notes are few — an integration always writes an error boundary, so `dotnet-error-handling` always appears here. Always include, verbatim, this hazard row: a body that does not match its declared type — a drifted or malformed **2xx** response (a missing `required` member) or a **non-2xx** body that does not match its operation's generated `{Operation}Error` shape — surfaces as `ResponseDeserializationException`, an `ApiException` that keeps the HTTP status and names the target type but is **not** an `ApiException`; a catch ladder that handles only `ApiException` lets it escape, so it must also catch `ResponseDeserializationException` (or `ApiException`). -5. **PRODUCTION READINESS** — the fixed table below, every row carrying a *decision*. Naming a skill in REQUIRED READING does not address a concern; it defers it. `N/A` is a legitimate answer where it is genuinely true, but it must carry its reason. -6. **Assumptions & Blockers** — anything you had to assume about the user's intent, and anything that blocks planning. An empty section is a valid outcome; an invented fact is not. If you expect the provider to reject a call the plan makes, that is a **Blocker**. -7. Every row's **source** cell cites its map page or the map-named declaring file, so a later lookup is one targeted open, not a search. A row with neither to cite is not a contract fact: write `YOUR CALL — not in the map` there instead. Three labels, one order — a fact the map settles cites that page or file; a fact only live traffic can settle is `UNVERIFIED`; a decision about the application is `YOUR CALL — not in the map`. Something that stops you planning goes in §6. - -| # | Concern | The decision the plan must record | -| --- | --- | --- | -| 1 | **Credential fail-fast** | Where credentials are bound, and that the host refuses to start when one is missing or blank — rather than discovering it as a 401 on the first call in production. A multi-part credential needs **every** part checked, because a blank part is not a missing one. | -| 2 | **Secret sourcing & rotation** | Where the secret comes from, and that the DI registration builds the options object **once at registration** and captures it in the singleton — so a rotated secret does not take effect until the process restarts. If rotation without a restart is required, say how. | -| 3 | **Total timeout budget** | The number the caller actually gets, not the knob. `Timeout` is **per attempt**, so a hung retryable call costs a multiple of it. State the budget and where it is enforced — a `CancellationToken` deadline is the only thing that bounds a whole call. | -| 4 | **Write-retry ownership** | Which of the scope's writes the SDK may resend and which it may not. The default `HttpMethodsToRetry` covers `GET, HEAD, PUT, OPTIONS`, so `POST`/`PATCH`/`DELETE` are never resent by the SDK — and `PUT` **is**. | -| 5 | **Idempotency & ambiguous writes** | For each write in scope: the key it uses, or that none exists. Whether an operation takes a REAL caller-supplied key — and its member name — is on that operation's request record: the record file its map row's **Type sources** table names. The generator-injected `Idempotency-Key` header (`Guid.NewGuid()`, fresh on every call) is **not** a key and must not be cited as one. Where no key exists, record the reconciliation path instead. | -| 6 | **Observability** | What is logged at which level, that **JSON request bodies are logged unredacted** when `LogRequestBody` is on, and which correlation id from the provider's error bodies reaches your own logs. | -| 7 | **Sensitive data** | Whether the scope carries data you would not want in a log — each request model's declaring file lists every field in scope. If it does: `LogRequestBody` stays off **and** `LoggerFactory` is assigned explicitly, so the SDK's log environment variable cannot switch it on from outside the code (see `dotnet-getting-started`). Form bodies are masked only by deny-list, so a key you have not listed prints in the clear. | -| 8 | **Environment selection** | Which base URLs each deployment talks to. Enumerate the server groups and environment members from the map's *Servers & auth* section — there may be more than one group, and each operation resolves through its own. State which groups the scope touches and what each deployment sets; if the SDK declares no sandbox environment, say how test traffic is kept away from the live system. | -| 9 | **Duplicate prevention under concurrency** | For each write a caller can trigger twice: its row in **DUPLICATE CLAIMS** below. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in §6, not a row. | -| 10 | **Partial results** | For each paged read that can stop at a cap: the field or return type that tells the caller the result was cut short. | -| 11 | **Unknown outcomes** | For each write whose connection can fail after the provider may have acted: how the code settles the outcome instead of reporting a failure. | - -A reviewer grades that table alone: each row either records a decision or records why it does not apply. A row that points at a skill, restates the concern, or sits blank is **not addressed**. Rows 5, 7, 8, 9 and 11 are the ones where "not addressed" costs something that cannot be recovered afterwards — a duplicate charge or duplicate write, sensitive data in a log, or test traffic sent to a live system. - -**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write. - -| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | -| --- | --- | --- | --- | --- | - -Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. - -⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it. - -⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces: a primary key is refused even by in-memory test stores; a unique index may not be. - -**Where that rejection is caught**: the `catch` for what the store raises on the second claim — a lookup that returns the earlier result is not a catch. - -Write `TBD` in **Where in the code** while you plan — every row. Once the code compiles, replace each `TBD` individually with two members, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a member that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. - -**PAGED READS** — one row per read that walks pages; `none` if the scope reads no paged list. - -| Read | What caps it | How the caller learns the answer was cut short | Where in the code | -| --- | --- | --- | --- | - -⚠⚠ A log line is not a legal entry in the third column: whoever reads the result never reads your logs. Name the field or return type that changes. Write `TBD` in **Where in the code** while you plan; once the code compiles, replace it with the member that sets that field or return type. A `TBD` left behind is not addressed. - -**UNKNOWN OUTCOMES** — one row per write whose connection can fail after the provider may already have acted; `none` if the scope makes no writes. - -| Write | The operation you re-read with | The reference you search by | Where in the code | The test that fails the connection | -| --- | --- | --- | --- | --- | - -⚠⚠ Reporting a failure is not a legal entry, and neither is leaving the caller to retry. Settle the outcome in the failing write's own `catch`, before it returns, or record it as unknown for code that settles it later — a report someone reads is not that code. With no reference, the third column names what the match uses; if the provider offers no way to find the write, that is a Blocker in §6, not a row. - -**Where in the code**: that `catch`, or the unknown state and the code that settles it — a re-read helper nothing calls is not addressed. **The test that fails the connection** asserts what follows. Both are `TBD` while you plan, replaced once the code compiles. A `TBD` left behind is not addressed. - -Keep the file lean: no copied map pages, no full model dumps, and no clone path — only the operations and fields the scope actually touches. - -### Step 2 — Required reading (do this before you write any code) - -The contract sheet ends with a **REQUIRED READING** block, and its rows carry inline `MUST load ` pointers. **Load every `dotnet-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. If the sheet names none, load `dotnet-error-handling` anyway — every integration writes an error boundary. - -These are API-agnostic usage skills; loading them is not the same as reading the map. Contract *facts* still come only from the sheet or a map lookup. - -Before implementing, check the plan's **Assumptions & Blockers** section: - -- Blocker or major assumption → surface it to the user in plain language, get their answer, then revise `alpaca-plan.md` in place with targeted edits — edit the changed rows, append the new section. Rewriting the whole file to change a few rows is a defect. -- Minor assumptions only → proceed. - -Full re-planning only on genuine scope change; for a single missing fact mid-implementation, look it up in the map — never guess. - -### Step 3 — Implement from the contract sheet - -1. Read `alpaca-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. A row whose source column says `YOUR CALL — not in the map` is the one exception: that row is a planning-time judgment about YOUR application, so weigh it against the task and follow the task. -2. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 2 — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire names, error accessors, enum values) from the contract sheet or a map lookup — never re-derive one from a companion. -3. After every change: `dotnet build`; fix non-SDK errors yourself. -4. **Any compile or runtime error involving an SDK type or member** (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Alpaca.*`, or a provider error at runtime) → *Step 4* below. Do not attempt more than one self-fix of an SDK-name error before switching to that procedure — rewriting from the same knowledge that produced the error is guessing. -5. Once the code is complete, open each member the **DUPLICATE CLAIMS** and **UNKNOWN OUTCOMES** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. -6. Run the project's tests (`dotnet test`); verify the integration end to end the way the task demands. - -### Step 4 — Fixing SDK errors (map-first, in place) - -A compile or runtime error involving an SDK type or member (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Alpaca.*`, or a provider error). Resolve it map-first — never patch it by guessing. Load `dotnet-getting-started` first if you have not already — the map is where every fix below comes from. - -1. **Map lookup first.** For an operation symbol, find its block on the controller's operations page; for a model, enum, error or request-record symbol, open the declaring file — its name is its path (`src/Alpaca/Models/{Type}.cs`, `src/Alpaca/Models/Enums/{Type}.cs`, `src/Alpaca/Errors/{Type}.cs`, `src/Alpaca/Requests/{Type}.cs`, or `src/Alpaca/Requests/{Controller}/{Type}.cs` for an operation on a controller — an operation with several tags is filed under the first tag it declares), or take it from the operation's Type sources table. If the code contradicts what you read (wrong field name, missed response envelope, wrong param order, wrong namespace), that is the fix. Response envelopes are the classic case: response types often wrap their payload in one field — reads go one level down. -2. **Go one level deeper on a real gap.** If what you read matches the code, or ambiguity remains, open the operation's own method in the `Api/{Controller}.cs` file the page header names — its `` docs and the request it builds — and the per-member docs on its request record, in this session's SDK clone, scoped. Never scan the tree. -3. **Never re-guess.** Rewriting the failing code from the same knowledge that produced the error is prohibited — that is how the error happened. Each failing symbol gets a map/source-grounded answer before its line changes; never mutate payloads, field names, or status handling speculatively to "see if it works". -4. **Fix in place** — edit only the project files involved in the error, grounded in the map row (or the named source file). -5. **Build to verify, and classify the outcome:** compiles clean → run `dotnet test` on the touched tests when they exist; compile error → the fix is not done, resolve it the same map-first way and rebuild; build blocked (output locked) → the solution is running, so stop it if you started it, or ask the user to rather than killing their process. -6. **Runtime / provider errors** — read the provider's error payload through the documented path: the operation's error case and `TryGet…` accessors from its map row; `dotnet-error-handling` for the Case A/B mechanics. Config-shaped failures (401, wrong host, timeouts): check auth, the server-node/base-URL configuration, and retry semantics before touching call sites. - -If a fix corrects a row in `alpaca-plan.md`, correct the row in the file too — the sheet stays the record of what was verified. - -### Step 5 — Pure questions - -A standalone Alpaca question with no code change: load `dotnet-getting-started`, look it up in the map (open the one source file the map names only on a real gap), and answer with the map page (or source file) it came from. No plan file — just the grounded answers; when several questions arrive batched, answer them all in one pass. Never answer from memory, even for "easy" questions. - -## Anti-patterns — never do these - -- **Never write a Alpaca/SDK fact from memory** — every signature, field name, enum value, and error type in your code must come from the contract sheet or a map lookup. And **never write a call from memory "to fix later".** -- **Do not re-derive or double-check a sheet row from memory** — re-open the one map page it cites. -- **Do not re-derive a contract fact from a `dotnet-*` companion** — they are usage guidance; facts come from the map. -- **Do not grep, glob or `find` the SDK tree, and do not open its `api-reference.md`** — the map is the locator; `dotnet-getting-started` says why. -- **Do not web-search Alpaca for an implementation detail** — the map and the SDK source it lives in are the ground truth for THIS SDK version, and `Alpaca` is what the generated source declares, not what public docs describe. -- **Do not resolve a trap note inline on the sheet** — name the hazard and the skill. - +--- +name: "dotnet-integrate-alpaca" +description: "MANDATORY FIRST STEP for Alpaca .NET SDK work in a C#/.NET project — load this BEFORE opening the SDK map or touching any project file; .NET/C# SDK ONLY, never load it for any other language. Applies when asked to integrate Alpaca in C# — AccountActivities, AccountConfigurationsApi, Accounts, CalendarApi, ClockApi, Orders, PortfolioHistoryApi, Positions, Watchlists — or when a Alpaca .NET SDK call errors or behaves unexpectedly. It carries four binding rules stated NOWHERE else — (1) for implementation work, the plan file alpaca-plan.md is written at the project repo root BEFORE any project file is created or edited; (2) dotnet-getting-started and the SDK map it carries are loaded and used for every contract fact, never memory; (3) every dotnet-* skill the contract sheet's REQUIRED READING names is loaded before implementation starts; (4) every sheet row carries one of three labels — a map page, UNVERIFIED, or YOUR CALL — not in the map. Skip it and SDK facts get written from stale memory." +--- + +# Alpaca .NET SDK — Workflow (map + skills) + +You are the Alpaca .NET SDK specialist for this task — one agent for every SDK need: planning, answering contract questions, implementing, and fixing SDK errors. Your scope is **C#/.NET only**: if a request concerns any other language or SDK, say so and stop. Your source of truth is the **SDK map that ships inside the SDK source repo** (`sdk-map.md` plus its `map/operations/` pages; model, enum and error shapes live in the source files those pages name), obtained per `dotnet-getting-started`, plus the companion `dotnet-*` skills for usage traps. Your training data on this SDK is stale — every fact you emit must come from a map page or a map-named source file you actually read this session, in that same clone. You never guess, and you never open the SDK's `api-reference.md`. You do all of it yourself — ground, plan, implement, fix — there is no helper agent to delegate to. + +**Scope guard:** the APIMatic-generated Alpaca **.NET SDK** (root namespace `Alpaca`) in **C#/.NET projects only**. Unrelated API, or any language other than C#/.NET — do nothing; this skill does not apply. + +## Grounding rules + +- **Map for contracts; the map-named file for shapes.** Follow `dotnet-getting-started`'s *SDK map* and *SDK source* sections in full — they define how to obtain the source (the map lives inside it), that reading a model/enum/error shape means opening the one declaring file the map names (routine, scoped), and why locating anything by grep/glob/`find` over the tree is a defect rather than a shortcut. The clone stays in the system temp directory, and `alpaca-plan.md` never carries its path — the plan must stay portable. +- **Never grep, glob or `find` the SDK tree to locate something** — the map is the locator, and a type name is its path. Every lookup starts at the map index and ends on the one page (or the one source file) it names. +- **Your contract sheet never leaves a contract fact open for "later."** The map pages and the files they name answer nearly everything — resolve them during planning, not mid-implementation. For the rare in-scope fact even the source cannot settle: if only live traffic could confirm it, convert it into a concrete defensive-coding directive on the sheet and label the uncertainty `UNVERIFIED`. Never leave a row marked for a later source lookup: an open row is how you end up opening source mid-implementation. +- **When you judge how far a contract can be trusted**, the trust judgment may cite ONLY evidence visible in the map or SDK source (e.g. two generated definitions that disagree, a suspicious shared model) — never training-data memory of this API, and never claims about what the live wire "usually" sends. +- **The sheet records Alpaca's call surface; the application's design is decided at implementation time.** Its persistence, its concurrency rules, and the request contract its own callers must satisfy are not SDK facts. Where an SDK fact forces an application decision, record the fact and its consequence on the sheet, mark the decision `YOUR CALL — not in the map`, and decide it at implementation time against the task. +- **Name only what you have read.** Never name a claim, header or route of the application as though you knew it exists — unless you have read that code. Name configuration by its binding key, never by a raw environment variable, and give the default the map documents where there is one: a setting you invent a name for is a setting no deployment will supply. + +## Workflow + +**If the user opens with a reported SDK error or unexpected Alpaca behaviour** (not new feature work), go straight to *Step 4 — Fixing SDK errors* below — do not run the plan-first flow for a bug report. Otherwise, for implementation work: + +### Step 1 — Plan first: write `alpaca-plan.md` (always, for any implementation work) + +Your FIRST deliverable is `alpaca-plan.md` at `/alpaca-plan.md` — that exact path, always; never pick another location. It carries the plan and the contract sheet in the format below, and it is the only project-repo file you write before the HARD GATE further down. + +#### How to ground (map-first, one pass) + +1. Load `dotnet-getting-started`; open `sdk-map.md` (the index). +2. From the index, open the **operations pages** for every controller in scope — take signatures (the request record each operation takes, and its required members), return types, error case (A: typed `ApiException<{Operation}Error>` with its `TryGet…` accessors and payload type / B: `ApiException`), and pagination — remembering that a block silent on a defaults-table point means the default applies. Where an operation's *semantics* decide what you must pass, that prose is the method's XML `` in the `Api/` file the page names, and the per-member docs on its request record: read it there. +3. Open, scoped, the **declaring source file** for every request/response model you will reference — the operation's **Type sources** table names it — and take field names WITH their `[JsonPropertyName]` wire names, `required` flags, nullability, and the envelope shape. Enum value lists and union accessors come from their declaring files the same way. +4. Identify which companion skill governs each step in scope. For each, write the trap note as a **named hazard plus a `MUST load` pointer** — *not* as the resolved answer. Naming the hazard ("what `Timeout` actually bounds") is right; resolving it inline ("`Timeout` is per-attempt") is wrong — a resolved trap gives you no reason to load the skill, and the skill carries the parts a one-line note cannot. **Never restate a companion skill's default or semantics in a trap note — not even when you believe it correct.** Write the *consequence*, not the answer. Contract *facts* are the opposite: those you resolve fully and inline. +5. Collect everything in ONE pass — the whole point is that you never have to rediscover a contract mid-coding. + +#### Prerequisites you can run alongside + +These need no SDK knowledge and touch no project file, so do them before or alongside the map work: + +- reaching the SDK source (per `dotnet-getting-started` — the map lives inside it, so this comes before any map work); +- the repo survey (read-only exploration of conventions and layering) — record each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; +- `dotnet restore` and a baseline `dotnet build` / `dotnet test` of the UNTOUCHED solution, so later failures are attributable to your changes; +- locating the SDK reference and its version in the project; +- credentials/environment verification (per the task's secret-handling rules); +- setting up your task tracking. + +Never use this prerequisite work as a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. + +#### HARD GATE + +**No project-file creation or edits until `alpaca-plan.md` exists at that path with every section below filled in.** Starting to code before the sheet exists defeats the plan-first design: the sheet is what keeps SDK facts out of your memory and in the map. The gate bars coding before the sheet exists; it does not bar the read-only prerequisite work above. + +#### The `alpaca-plan.md` format (keep it tight — tables, not prose) + +1. **Scope & sequence** — the implementation steps in order, each naming the operations it uses. A capability the map lacks is a Blocker (§6), never a data path you invent to replace it. +2. **CONTRACT SHEET** — open the section with two literal warning lines: that signatures are generated code, verbatim — each operation that takes input takes ONE request record as its first parameter (an operation with no inputs takes none), built with an object initializer whose property names are the record's own, never flat arguments; and that every SDK type is written fully-qualified with the namespace its source path implies, taken from the path the map gives for THAT type, never from where a neighbouring type sits. Then one table row per operation: controller property · method signature · request record + its members (`Name: type, required?`) · body model + its fields (`Name (wire_name): type, required?`) · response envelope + the inner fields the integration reads · error case A/B + accessors + payload type · pagination · **source** (§7). Below it: the enum value tables actually needed, and the client construction/auth/server-node facts. ⚠ A request model may mark nothing required, and then `required?` selects nothing for you — carry the optional fields the endpoint's own prose ties to whether the call is accepted, and say which doc-named fields you left out. + +The CONTRACT SHEET also carries a **CROSS-OPERATION INVARIANTS** table, directly below its per-operation rows: the rules no single row can state, because they relate two operations. One row per value the task lets a caller supply where a *different* operation defines the legal set for it (identifiers, handles, codes, references); `none` if the task has no such value — never leave the table out. + +| Invariant | Operations | Enforced where | +| --- | --- | --- | +| a value accepted by X must be one Y returned | `{WriteOperation}` ← `{ListOperation}` | before `{WriteOperation}` is called | + +⚠⚠ The map never states these rules; derive them from the task. + +⚠⚠ **Operations** names the operation that *defines* the legal set: the list or search call that returns what this application offers, or the earlier call whose result this application stored. A lookup that confirms a single value exists is not an answer: it proves the value exists at the provider, not that it is one this application offers. + +**Enforced where** names the member that checks, before the write, that the caller's value is in that set. If the application never reads that set, say so there and add the read — the check is part of the work. `YOUR CALL — not in the map` is not a legal entry in any cell of this table. + +3. **Trap notes** — one line per hazard, attached to the step where it bites, each ending in an inline **`MUST load `** pointer. Name the hazard and its consequence; do not resolve it. ⚠⚠ **"Do not resolve it" is the load-bearing half of this rule.** A trap note that answers its own question hands you a usable one-liner — and holding one, you do not open the skill. You implement from the sentence, and everything the skill carries beyond it never reaches the code. So: state the hazard, state what it costs, hand over the skill, and stop. No fix, no snippet, not even a partial answer. A trap note from which you could write correct code without loading the named skill is a defect. +4. **REQUIRED READING** — close the sheet with the de-duplicated list of every `dotnet-*` skill named above, one line each: skill · the step it governs. Write each name so the copy is unambiguous — every APIMatic .NET plugin ships these same skill names, and a bare name gives you no way to tell which one you loaded. State that these are to be loaded **before implementation starts**, and that the sheet deliberately does not carry their contents. This block is mandatory even when the trap notes are few — an integration always writes an error boundary, so `dotnet-error-handling` always appears here. Always include, verbatim, this hazard row: a body that does not match its declared type — a drifted or malformed **2xx** response (a missing `required` member) or a **non-2xx** body that does not match its operation's generated `{Operation}Error` shape — surfaces as `ResponseDeserializationException`, an `ApiException` that keeps the HTTP status and names the target type but is **not** an `ApiException`; a catch ladder that handles only `ApiException` lets it escape, so it must also catch `ResponseDeserializationException` (or `ApiException`). +5. **PRODUCTION READINESS** — the fixed table below, every row carrying a *decision*. Naming a skill in REQUIRED READING does not address a concern; it defers it. `N/A` is a legitimate answer where it is genuinely true, but it must carry its reason. +6. **Assumptions & Blockers** — anything you had to assume about the user's intent, and anything that blocks planning. An empty section is a valid outcome; an invented fact is not. If you expect the provider to reject a call the plan makes, that is a **Blocker**. +7. Every row's **source** cell cites its map page or the map-named declaring file, so a later lookup is one targeted open, not a search. A row with neither to cite is not a contract fact: write `YOUR CALL — not in the map` there instead. Three labels, one order — a fact the map settles cites that page or file; a fact only live traffic can settle is `UNVERIFIED`; a decision about the application is `YOUR CALL — not in the map`. Something that stops you planning goes in §6. + +| # | Concern | The decision the plan must record | +| --- | --- | --- | +| 1 | **Credential fail-fast** | Where credentials are bound, and that the host refuses to start when one is missing or blank — rather than discovering it as a 401 on the first call in production. A multi-part credential needs **every** part checked, because a blank part is not a missing one. | +| 2 | **Secret sourcing & rotation** | Where the secret comes from, and that the DI registration builds the options object **once at registration** and captures it in the singleton — so a rotated secret does not take effect until the process restarts. If rotation without a restart is required, say how. | +| 3 | **Total timeout budget** | The number the caller actually gets, not the knob. `Timeout` is **per attempt**, so a hung retryable call costs a multiple of it. State the budget and where it is enforced — a `CancellationToken` deadline is the only thing that bounds a whole call. | +| 4 | **Write-retry ownership** | Which of the scope's writes the SDK may resend and which it may not. The default `HttpMethodsToRetry` covers `GET, HEAD, PUT, OPTIONS`, so `POST`/`PATCH`/`DELETE` are never resent by the SDK — and `PUT` **is**. | +| 5 | **Idempotency & ambiguous writes** | For each write in scope: the key it uses, or that none exists. Whether an operation takes a REAL caller-supplied key — and its member name — is on that operation's request record: the record file its map row's **Type sources** table names. The generator-injected `Idempotency-Key` header (`Guid.NewGuid()`, fresh on every call) is **not** a key and must not be cited as one. Where no key exists, record the reconciliation path instead. | +| 6 | **Observability** | What is logged at which level, that **JSON request bodies are logged unredacted** when `LogRequestBody` is on, and which correlation id from the provider's error bodies reaches your own logs. | +| 7 | **Sensitive data** | Whether the scope carries data you would not want in a log — each request model's declaring file lists every field in scope. If it does: `LogRequestBody` stays off **and** `LoggerFactory` is assigned explicitly, so the SDK's log environment variable cannot switch it on from outside the code (see `dotnet-getting-started`). Form bodies are masked only by deny-list, so a key you have not listed prints in the clear. | +| 8 | **Environment selection** | Which base URLs each deployment talks to. Enumerate the server groups and environment members from the map's *Servers & auth* section — there may be more than one group, and each operation resolves through its own. State which groups the scope touches and what each deployment sets; if the SDK declares no sandbox environment, say how test traffic is kept away from the live system. | +| 9 | **Duplicate prevention under concurrency** | For each write a caller can trigger twice: its row in **DUPLICATE CLAIMS** below. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in §6, not a row. | +| 10 | **Partial results** | For each paged read that can stop at a cap: the field or return type that tells the caller the result was cut short. | +| 11 | **Unknown outcomes** | For each write whose connection can fail after the provider may have acted: how the code settles the outcome instead of reporting a failure. | + +A reviewer grades that table alone: each row either records a decision or records why it does not apply. A row that points at a skill, restates the concern, or sits blank is **not addressed**. Rows 5, 7, 8, 9 and 11 are the ones where "not addressed" costs something that cannot be recovered afterwards — a duplicate charge or duplicate write, sensitive data in a log, or test traffic sent to a live system. + +**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write. + +| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | +| --- | --- | --- | --- | --- | + +Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. + +⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it. + +⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces: a primary key is refused even by in-memory test stores; a unique index may not be. + +**Where that rejection is caught**: the `catch` for what the store raises on the second claim — a lookup that returns the earlier result is not a catch. + +Write `TBD` in **Where in the code** while you plan — every row. Once the code compiles, replace each `TBD` individually with two members, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a member that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. + +**PAGED READS** — one row per read that walks pages; `none` if the scope reads no paged list. + +| Read | What caps it | How the caller learns the answer was cut short | Where in the code | +| --- | --- | --- | --- | + +⚠⚠ A log line is not a legal entry in the third column: whoever reads the result never reads your logs. Name the field or return type that changes. Write `TBD` in **Where in the code** while you plan; once the code compiles, replace it with the member that sets that field or return type. A `TBD` left behind is not addressed. + +**UNKNOWN OUTCOMES** — one row per write whose connection can fail after the provider may already have acted; `none` if the scope makes no writes. + +| Write | The operation you re-read with | The reference you search by | Where in the code | The test that fails the connection | +| --- | --- | --- | --- | --- | + +⚠⚠ Reporting a failure is not a legal entry, and neither is leaving the caller to retry. Settle the outcome in the failing write's own `catch`, before it returns, or record it as unknown for code that settles it later — a report someone reads is not that code. With no reference, the third column names what the match uses; if the provider offers no way to find the write, that is a Blocker in §6, not a row. + +**Where in the code**: that `catch`, or the unknown state and the code that settles it — a re-read helper nothing calls is not addressed. **The test that fails the connection** asserts what follows. Both are `TBD` while you plan, replaced once the code compiles. A `TBD` left behind is not addressed. + +Keep the file lean: no copied map pages, no full model dumps, and no clone path — only the operations and fields the scope actually touches. + +### Step 2 — Required reading (do this before you write any code) + +The contract sheet ends with a **REQUIRED READING** block, and its rows carry inline `MUST load ` pointers. **Load every `dotnet-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. If the sheet names none, load `dotnet-error-handling` anyway — every integration writes an error boundary. + +These are API-agnostic usage skills; loading them is not the same as reading the map. Contract *facts* still come only from the sheet or a map lookup. + +Before implementing, check the plan's **Assumptions & Blockers** section: + +- Blocker or major assumption → surface it to the user in plain language, get their answer, then revise `alpaca-plan.md` in place with targeted edits — edit the changed rows, append the new section. Rewriting the whole file to change a few rows is a defect. +- Minor assumptions only → proceed. + +Full re-planning only on genuine scope change; for a single missing fact mid-implementation, look it up in the map — never guess. + +### Step 3 — Implement from the contract sheet + +1. Read `alpaca-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. A row whose source column says `YOUR CALL — not in the map` is the one exception: that row is a planning-time judgment about YOUR application, so weigh it against the task and follow the task. +2. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 2 — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire names, error accessors, enum values) from the contract sheet or a map lookup — never re-derive one from a companion. +3. After every change: `dotnet build`; fix non-SDK errors yourself. +4. **Any compile or runtime error involving an SDK type or member** (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Alpaca.*`, or a provider error at runtime) → *Step 4* below. Do not attempt more than one self-fix of an SDK-name error before switching to that procedure — rewriting from the same knowledge that produced the error is guessing. +5. Once the code is complete, open each member the **DUPLICATE CLAIMS** and **UNKNOWN OUTCOMES** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. +6. Run the project's tests (`dotnet test`); verify the integration end to end the way the task demands. + +### Step 4 — Fixing SDK errors (map-first, in place) + +A compile or runtime error involving an SDK type or member (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Alpaca.*`, or a provider error). Resolve it map-first — never patch it by guessing. Load `dotnet-getting-started` first if you have not already — the map is where every fix below comes from. + +1. **Map lookup first.** For an operation symbol, find its block on the controller's operations page; for a model, enum, error or request-record symbol, open the declaring file — its name is its path (`src/Alpaca/Models/{Type}.cs`, `src/Alpaca/Models/Enums/{Type}.cs`, `src/Alpaca/Errors/{Type}.cs`, `src/Alpaca/Requests/{Type}.cs`, or `src/Alpaca/Requests/{Controller}/{Type}.cs` for an operation on a controller — an operation with several tags is filed under the first tag it declares), or take it from the operation's Type sources table. If the code contradicts what you read (wrong field name, missed response envelope, wrong param order, wrong namespace), that is the fix. Response envelopes are the classic case: response types often wrap their payload in one field — reads go one level down. +2. **Go one level deeper on a real gap.** If what you read matches the code, or ambiguity remains, open the operation's own method in the `Api/{Controller}.cs` file the page header names — its `` docs and the request it builds — and the per-member docs on its request record, in this session's SDK clone, scoped. Never scan the tree. +3. **Never re-guess.** Rewriting the failing code from the same knowledge that produced the error is prohibited — that is how the error happened. Each failing symbol gets a map/source-grounded answer before its line changes; never mutate payloads, field names, or status handling speculatively to "see if it works". +4. **Fix in place** — edit only the project files involved in the error, grounded in the map row (or the named source file). +5. **Build to verify, and classify the outcome:** compiles clean → run `dotnet test` on the touched tests when they exist; compile error → the fix is not done, resolve it the same map-first way and rebuild; build blocked (output locked) → the solution is running, so stop it if you started it, or ask the user to rather than killing their process. +6. **Runtime / provider errors** — read the provider's error payload through the documented path: the operation's error case and `TryGet…` accessors from its map row; `dotnet-error-handling` for the Case A/B mechanics. Config-shaped failures (401, wrong host, timeouts): check auth, the server-node/base-URL configuration, and retry semantics before touching call sites. + +If a fix corrects a row in `alpaca-plan.md`, correct the row in the file too — the sheet stays the record of what was verified. + +### Step 5 — Pure questions + +A standalone Alpaca question with no code change: load `dotnet-getting-started`, look it up in the map (open the one source file the map names only on a real gap), and answer with the map page (or source file) it came from. No plan file — just the grounded answers; when several questions arrive batched, answer them all in one pass. Never answer from memory, even for "easy" questions. + +## Anti-patterns — never do these + +- **Never write a Alpaca/SDK fact from memory** — every signature, field name, enum value, and error type in your code must come from the contract sheet or a map lookup. And **never write a call from memory "to fix later".** +- **Do not re-derive or double-check a sheet row from memory** — re-open the one map page it cites. +- **Do not re-derive a contract fact from a `dotnet-*` companion** — they are usage guidance; facts come from the map. +- **Do not grep, glob or `find` the SDK tree, and do not open its `api-reference.md`** — the map is the locator; `dotnet-getting-started` says why. +- **Do not web-search Alpaca for an implementation detail** — the map and the SDK source it lives in are the ground truth for THIS SDK version, and `Alpaca` is what the generated source declares, not what public docs describe. +- **Do not resolve a trap note inline on the sheet** — name the hazard and the skill. + diff --git a/plugins/alpaca/skills/python/python-getting-started/SKILL.md b/plugins/alpaca/skills/python/python-getting-started/SKILL.md index b8b5c08b..3f78285a 100644 --- a/plugins/alpaca/skills/python/python-getting-started/SKILL.md +++ b/plugins/alpaca/skills/python/python-getting-started/SKILL.md @@ -1,231 +1,231 @@ ---- -name: "python-getting-started" -description: "Alpaca Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Alpaca Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Alpaca Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Alpaca Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-alpaca` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `alpaca/` and `pyproject.toml` of the generated package at version `2.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Alpaca | -| Distribution name (what you install) | `alpaca` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `alpaca` — the same string you install | -| Source repository | https://github.com/context-plugins/alpaca-python-sdk | -| Source branch | `main` | -| Version | `2.0.0` | -| Sync client class | `AlpacaClient` (alias `Client`) | -| Async client class | `AsyncAlpacaClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key` · `api_secret`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **API key** in the `APCA-API-KEY-ID` header — set `api_key` · **API key** in the `APCA-API-SECRET-KEY` header — set `api_secret` | -| Environments | 2 environments selected by `environment` (default `"paper"`), overridable with `base_url` | -| Base-URL config | `ServerConfig` (`alpaca/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 29 operations across 9 controllers · 16 models · 2 unions · 14 enums · 4 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "alpaca @ git+https://github.com/context-plugins/alpaca-python-sdk.git@main" -python -c "import alpaca, pathlib; print(pathlib.Path(alpaca.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from alpaca import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`alpaca/__init__.py` exports exactly 6 names beside the `alpaca.models` subpackage it re-exports: - -```python -from alpaca import ( - AlpacaClient, - AsyncAlpacaClient, - AsyncClient, - Client, - Environment, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `alpaca.models` | -| Enums (and their open `…OrStr` aliases) | `alpaca.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `alpaca.core` | -| Per-operation error *unions* | `alpaca.errors` (`DeleteAllOpenPositionsErrorBody`, …) | - -`alpaca.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments - -The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: - -| `environment=` | Base URL | Hosting | -| --- | --- | --- | -| `"paper"` *(default)* | `https://paper-api.alpaca.markets` | Paper trading — simulated money. Safe default | -| `"live"` | `https://api.alpaca.markets` | Live trading — REAL money | - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `environment` gives you **`"paper"`**, silently — it is whatever the description listed first, not necessarily production. -- `base_url` overrides the environment's URL entirely when both are passed. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (two schemes) - -An API key sent as the `APCA-API-KEY-ID` header, exposed as the client's `api_key=` keyword taking a plain string. - -```python -from alpaca import Client - -client = Client(api_key="") -``` - -An API key sent as the `APCA-API-SECRET-KEY` header, exposed as the client's `api_secret=` keyword taking a plain string. - -```python -from alpaca import Client - -client = Client(api_secret="") -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.account_activities` | `AccountActivities` / `AsyncAccountActivities` | 2 | `get_account_activities` · `get_account_activities_by_activity_type` | -| `client.account_configurations_api` | `AccountConfigurationsApi` / `AsyncAccountConfigurationsApi` | 2 | `get_account_config` · `patch_account_config` | -| `client.accounts` | `Accounts` / `AsyncAccounts` | 1 | `get_account` | -| `client.calendar_api` | `CalendarApi` / `AsyncCalendarApi` | 1 | `get_calendar` | -| `client.clock_api` | `ClockApi` / `AsyncClockApi` | 1 | `get_clock` | -| `client.orders` | `Orders` / `AsyncOrders` | 6 | `delete_all_orders` · `delete_order_by_order_id` · `get_all_orders` · `get_order_by_order_id` · `patch_order_by_order_id` · `post_order` | -| `client.portfolio_history_api` | `PortfolioHistoryApi` / `AsyncPortfolioHistoryApi` | 1 | `get_account_portfolio_history` | -| `client.positions` | `Positions` / `AsyncPositions` | 4 | `delete_all_open_positions` · `delete_open_position` · `get_all_open_positions` · `get_open_position` | -| `client.watchlists` | `Watchlists` / `AsyncWatchlists` | 11 | `add_asset_to_watchlist` · `add_asset_to_watchlist_by_name` · `delete_watchlist_by_id` · `delete_watchlist_by_name` · `get_watchlist_by_id` · `get_watchlist_by_name` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `alpaca/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `alpaca/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/alpaca-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `alpaca/models/account.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `alpaca/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import alpaca, pathlib; print(pathlib.Path(alpaca.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/alpaca` on Windows, `.venv/lib/python3.*/site-packages/alpaca` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `api_key=` and `api_secret=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **3 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 3 operations that return `None`** — `orders.delete_order_by_order_id` · `watchlists.delete_watchlist_by_id` · `watchlists.delete_watchlist_by_name`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 19 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which environment the `environment` keyword selects**, because omitting it is silently `"paper"` of the 2 declared. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Alpaca Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Alpaca Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Alpaca Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Alpaca Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-alpaca` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `alpaca/` and `pyproject.toml` of the generated package at version `2.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Alpaca | +| Distribution name (what you install) | `alpaca` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `alpaca` — the same string you install | +| Source repository | https://github.com/context-plugins/alpaca-python-sdk | +| Source branch | `main` | +| Version | `2.0.0` | +| Sync client class | `AlpacaClient` (alias `Client`) | +| Async client class | `AsyncAlpacaClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key` · `api_secret`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **API key** in the `APCA-API-KEY-ID` header — set `api_key` · **API key** in the `APCA-API-SECRET-KEY` header — set `api_secret` | +| Environments | 2 environments selected by `environment` (default `"paper"`), overridable with `base_url` | +| Base-URL config | `ServerConfig` (`alpaca/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 29 operations across 9 controllers · 16 models · 2 unions · 14 enums · 4 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "alpaca @ git+https://github.com/context-plugins/alpaca-python-sdk.git@main" +python -c "import alpaca, pathlib; print(pathlib.Path(alpaca.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from alpaca import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`alpaca/__init__.py` exports exactly 6 names beside the `alpaca.models` subpackage it re-exports: + +```python +from alpaca import ( + AlpacaClient, + AsyncAlpacaClient, + AsyncClient, + Client, + Environment, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `alpaca.models` | +| Enums (and their open `…OrStr` aliases) | `alpaca.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `alpaca.core` | +| Per-operation error *unions* | `alpaca.errors` (`DeleteAllOpenPositionsErrorBody`, …) | + +`alpaca.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments + +The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: + +| `environment=` | Base URL | Hosting | +| --- | --- | --- | +| `"paper"` *(default)* | `https://paper-api.alpaca.markets` | Paper trading — simulated money. Safe default | +| `"live"` | `https://api.alpaca.markets` | Live trading — REAL money | + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `environment` gives you **`"paper"`**, silently — it is whatever the description listed first, not necessarily production. +- `base_url` overrides the environment's URL entirely when both are passed. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (two schemes) + +An API key sent as the `APCA-API-KEY-ID` header, exposed as the client's `api_key=` keyword taking a plain string. + +```python +from alpaca import Client + +client = Client(api_key="") +``` + +An API key sent as the `APCA-API-SECRET-KEY` header, exposed as the client's `api_secret=` keyword taking a plain string. + +```python +from alpaca import Client + +client = Client(api_secret="") +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.account_activities` | `AccountActivities` / `AsyncAccountActivities` | 2 | `get_account_activities` · `get_account_activities_by_activity_type` | +| `client.account_configurations_api` | `AccountConfigurationsApi` / `AsyncAccountConfigurationsApi` | 2 | `get_account_config` · `patch_account_config` | +| `client.accounts` | `Accounts` / `AsyncAccounts` | 1 | `get_account` | +| `client.calendar_api` | `CalendarApi` / `AsyncCalendarApi` | 1 | `get_calendar` | +| `client.clock_api` | `ClockApi` / `AsyncClockApi` | 1 | `get_clock` | +| `client.orders` | `Orders` / `AsyncOrders` | 6 | `delete_all_orders` · `delete_order_by_order_id` · `get_all_orders` · `get_order_by_order_id` · `patch_order_by_order_id` · `post_order` | +| `client.portfolio_history_api` | `PortfolioHistoryApi` / `AsyncPortfolioHistoryApi` | 1 | `get_account_portfolio_history` | +| `client.positions` | `Positions` / `AsyncPositions` | 4 | `delete_all_open_positions` · `delete_open_position` · `get_all_open_positions` · `get_open_position` | +| `client.watchlists` | `Watchlists` / `AsyncWatchlists` | 11 | `add_asset_to_watchlist` · `add_asset_to_watchlist_by_name` · `delete_watchlist_by_id` · `delete_watchlist_by_name` · `get_watchlist_by_id` · `get_watchlist_by_name` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `alpaca/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `alpaca/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/alpaca-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `alpaca/models/account.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `alpaca/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import alpaca, pathlib; print(pathlib.Path(alpaca.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/alpaca` on Windows, `.venv/lib/python3.*/site-packages/alpaca` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `api_key=` and `api_secret=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **3 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 3 operations that return `None`** — `orders.delete_order_by_order_id` · `watchlists.delete_watchlist_by_id` · `watchlists.delete_watchlist_by_name`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 19 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which environment the `environment` keyword selects**, because omitting it is silently `"paper"` of the 2 declared. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/alpaca/skills/python/python-integrate-alpaca/SKILL.md b/plugins/alpaca/skills/python/python-integrate-alpaca/SKILL.md index 76852ad3..5666c8cb 100644 --- a/plugins/alpaca/skills/python/python-integrate-alpaca/SKILL.md +++ b/plugins/alpaca/skills/python/python-integrate-alpaca/SKILL.md @@ -1,97 +1,97 @@ ---- -name: "python-integrate-alpaca" -description: "MANDATORY FIRST STEP for Alpaca Python SDK work in a Python project — load this BEFORE you write any code; Python SDK ONLY, never load it for any other language. Applies when asked to integrate Alpaca in Python — gets the current account configuration values, returns the account associated with the api key, returns the market calendar, retrieves a single order for the given order_id, returns a watchlist identified by the id, returns a watchlist by name, delete one entry for an asset by symbol name, update the name and/or content of watchlist — or when a Alpaca Python SDK call errors or behaves unexpectedly. Knowing the SDK exists is NOT a substitute for loading this, because it carries five binding gates stated NOWHERE else and not inferable from the package — (1) load python-getting-started, confirm the package is actually installed and read the SDK map that ships with it before any lookup, (2) the exact plan-file path and the no-project-file-edits window until a contract sheet with no open lookups exists there and you have read it, (3) the mandatory load of every python-* companion skill the sheet names, (4) sync-vs-async decided once from the host application before the first call, and (5) the memory ban, where every signature, wire alias, error union and enum member comes from a lookup and never from recall or runtime introspection." ---- - -# Alpaca Python SDK — Integration workflow (lookup layer + contract sheet) - -You do the SDK lookups yourself: `python-getting-started` is your **lookup layer** — load it and ground every contract fact in it (and in the source modules its table names) before writing code. The `python-*` companion skills are a different thing: they are API-agnostic *usage* guidance, they are yours to load, and Step 1c below makes loading them mandatory. - -## Your lookup layer - -- **`python-getting-started`** is the entry point for every SDK need. It carries the SDK's identity (distribution name vs import root, version, Python floor, install-from-source), what the root package exports and the four modules the surface splits across, environments and the base-URL knob, the auth pattern, the controllers, and a **module table** naming the one file that owns each kind of fact. Load it first, always. -- **The SDK map is your lookup surface, and you open it yourself.** The SDK ships `sdk-map.md` and `map/operations/` at its root. There is no helper agent on the Python side — the map is yours to read, not something to delegate. `python-getting-started` tells you how to traverse it: read `sdk-map.md` once, then the one controller page your operations live on. -- **The installed package is the ground truth; the module table is only the locator.** Confirm the package is importable before you rely on a lookup — `python -c "import alpaca, pathlib; print(pathlib.Path(alpaca.__file__).parent)"`. **If it is not installed, install it first** — `python-getting-started` carries the command; until then mark the fact `UNVERIFIED`, say what would settle it, and do not fill the hole from memory. (The distribution is not on any package index — it installs from its repository; `python-getting-started` carries the command.) -- **Read scoped.** Those modules carry long design docstrings. `grep -n` for the symbol and read the surrounding lines rather than whole files, and never copy a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. -- **Write a contract sheet with no open lookups** before you implement: exact signatures, the keyword-only boundary, wire aliases, required-vs-`UNSET` members, the `ApiError.error` union per operation, and enum members for the operations in scope. `python-getting-started` ends with a checklist of the rows a Python sheet is incomplete without — treat that list as the checklist for your own sheet, and collect every in-scope operation in ONE pass rather than re-opening a module per member. - -**Scope guard:** the APIMatic-generated Alpaca **Python SDK** (import root `alpaca`, distribution `alpaca`) in **Python projects only**. Unrelated API, or any language other than Python — do nothing; this skill does not apply. - -## Workflow - -**If the user opens with a reported SDK error or unexpected Alpaca behaviour** (not new feature work), skip the plan-first flow: load `python-getting-started` and `python-error-handling`, look the failing symbol up in the module the table names, and fix from what you find. Otherwise, for implementation work: - -### Step 1 — Plan first (always, for any implementation work) - -Your FIRST action is to load `python-getting-started` and work through the user's full request (all features in scope — one plan covers the whole implementation). Then write the plan and its contract sheet to `/alpaca-plan.md` — that absolute path, not a location you pick later. - -**Do the read-only prerequisites in the same pass — they need no SDK knowledge and touch no project file:** - -- the repo survey (read-only exploration of conventions, layering, and **whether the project is sync or `async`**) — capture each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; -- **establish the toolchain before you need it**: which environment manager the repo uses (`uv`, `poetry`, `pip` + venv, `pdm`), whether `alpaca` is already a dependency and at what version, and the exact commands that run the project's tests and type checker. Getting this wrong later costs a broken install mid-implementation — and an uninstalled package costs you the lookup layer itself; -- a baseline run of the project's checks (`pytest`, `mypy`, `ruff` — whatever it has) on the UNTOUCHED tree, so later failures are attributable to your changes; -- credentials/environment verification (per the task's secret-handling rules) — including which host `environment` will select, since omitting it is silent; -- **a read-only smoke of each in-scope operation against the real credential**, run from your scratchpad, not the project, once the map has given you the signatures and before you finalise the sheet. A plan-, region- or entitlement-gated endpoint answers `403` here — the map presents every operation uniformly and cannot know which ones your key may call — and learning that now reorders the plan rather than the build. Skip only operations whose side effects you cannot reverse; -- setting up your task tracking. - -Never use the planning phase to get a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. - -**HARD GATE — no project-file creation or edits until:** `alpaca-plan.md` EXISTS at the repo root, its contract sheet has **no open lookups**, and you have read it. The gate bars coding "meanwhile"; it does not bar the read-only prerequisite work above. Starting to code before the sheet exists defeats the entire plan-first design. - -**What the gate does not cover:** creating or activating a virtual environment, installing `alpaca` and the toolchain into it, and the files those writes produce (`.venv/`, a lockfile the installer updates) are **prerequisites, not project-file edits**. On a greenfield repo they are the only way to reach the lookup layer at all, and they are the only writes permitted before the gate. Nothing you author by hand may change. - -**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write (`python-configuration-resilience`, *The same operation arriving twice*). - -| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | -| --- | --- | --- | --- | --- | - -Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. - -⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it; the SDK's own `Idempotency-Key` header is not one — it is new on every call. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in the plan's **Assumptions & Blockers**, not a row. - -⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces. - -**Where that rejection is caught**: the `except` for what the store raises on the second claim — a lookup that returns the earlier result is not an `except`. - -Write `TBD` in **Where in the code** while you plan — every row; it names code not yet written, not a contract fact, so it is not an open lookup. Once the code type-checks, replace each `TBD` individually with two functions, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a function that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. - -### Step 1c — Required reading (do this before you write any code) - -End the contract sheet with a **REQUIRED READING** block whose rows carry inline `MUST load ` pointers. **Load every `python-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. - -**A fixed floor applies regardless of what the sheet says** — the sheet can add to it, never remove from it: `python-error-handling` always (every integration writes an error boundary); `python-client-initialization` before the client is constructed; `python-testing` before the first test file you create or edit, *including* a throwaway verification script that fakes the transport. A sheet row that excuses one of these ("out of scope", "no test suite requested") is not a substitute for loading it. - -These are API-agnostic usage skills; loading them is not a substitute for the lookup layer, and reading a module in the installed package does not remove the need to load the skill for that step. Contract *facts* still come only from your sheet or a fresh lookup. - -Before implementing, check the plan's **Assumptions & Blockers** section: - -- Blocker or major assumption → surface it to the user in plain language, get their answer, and revise `alpaca-plan.md` in place. -- Minor assumptions only → proceed. - -Full re-planning only on genuine scope change; for a single missing fact mid-implementation, do the lookup, never guess. - -### Step 2 — Implement from the contract sheet - -1. Read `alpaca-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. When a lookup revises a row, update the file so the sheet and the code never disagree. -2. **Decide sync vs async once, from the host application, before the first call** — and take the decision from the repo survey, not from preference. This SDK ships two complete client classes (`Client` / `AsyncClient`) and there is no bridge between them: a sync client in an `async def` blocks the event loop, and an async client in sync code is a coroutine nobody awaits. Getting this wrong is not a local fix later; it is every call site. The choice also changes the transport-override keyword (`custom_http_client` vs `custom_async_http_client`) and the teardown obligation (`close()` vs `await aclose()`). -3. **Pick the response mode per call, deliberately.** Each operation exists twice: the plain call raises `ApiError` on failure, and its `with_raw_response` peer returns `ApiResult` instead. For the operations that return `None`, the raw peer is the only way to observe the status code — so a call whose outcome you must inspect needs the mode chosen at write time, not retrofitted. -4. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 1c — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire aliases, error unions, enum members) from the contract sheet or a fresh lookup — never re-derive one from a companion. -5. After every change: run the project's **type checker** (`mypy`/`pyright`) as well as its tests. The package ships `py.typed` and is generated under `mypy --strict`, so a type error against it is a real contract violation, not noise — it is the closest thing Python gives you to the compile step. **Treat a clean type check as the gate you do not skip.** If the project has no type checker configured, install `mypy` into the environment and run `mypy --strict` on the files you touched — the SDK is generated under that setting, so it checks cleanly against it. If you genuinely cannot install one, say so in your final report rather than silently substituting a smoke run. Fix non-SDK errors yourself. -6. **Any error involving an SDK type or member** — an `AttributeError`/`TypeError` on a `alpaca.*` object, a `pydantic.ValidationError` you cannot place, a mypy error naming an SDK type, an unexpected `ApiError` — → go back to `python-getting-started`'s module table and read the one module that owns the fact. Do not attempt more than one self-fix of an SDK-name error before doing that lookup: rewriting from the same knowledge that produced the error is guessing. Remember that a **decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes**, and that `httpx2` transport exceptions arrive unwrapped — so an exception that does not look like an API error may still be one of the SDK's failure kinds. -7. Once the code is complete, open each function the **DUPLICATE CLAIMS** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. -8. Run the project's tests; verify the integration end to end the way the task demands. Retrying is **on by default**, so decide whether the task keeps it, tunes it through `retry_options`, or turns it off with `max_retries` set to `0`, rather than wrapping the call in a retry of your own — and say which you did. - -### Step 3 — Answering pure questions - -A standalone Alpaca question with no code change: look it up in `python-getting-started` (and the module its table names), then give the grounded answer. Never answer from memory, even for "easy" questions. - -## Anti-patterns — never do these - -- **Always load `python-getting-started` first.** It is your lookup layer, not optional background. (The `python-*` companions are the complement: load the ones the sheet names, per Step 1c.) Don't re-derive a contract *fact* from a companion — exact signatures, wire aliases, error unions and enum members come from the sheet or a fresh lookup. -- **Never write a Alpaca/SDK fact from memory** — every signature, field name, enum member, and error union in your code must come from the contract sheet or a lookup. And **never write a call from memory "to fix later".** -- **Don't web-search Alpaca topics to find an implementation detail** — the installed package's own source is the ground truth, and the module table tells you where to look. Public Alpaca docs describe the REST API, not this SDK's generated surface, and the two disagree on names. -- **Never introspect the SDK at runtime to discover its shape.** `dir()`, `model_fields`, `inspect.signature`, or a REPL poke is the Python-flavoured version of decompiling the package: it answers what exists, never what is *supported*, and it silently invites private attributes into your code. Read the source module instead. -- **Don't grep the package to *locate* a type** — the SDK map is the locator (and says so: *"Never grep for a type"*); grep only *inside* the module its **Type sources** table names, for the symbol. A sweep for a *shape* is a different thing, and is fine: "every member typed `Optional[Any]`", "every discriminator field" are cross-cutting questions nothing indexes, and one targeted `grep -rn` over `models/` is the right tool for them — record what it found on the sheet. -- **Don't vendor, `sys.path`-hack, or editable-install the SDK from a throwaway clone** to make a lookup possible. Install the distribution properly into the project's environment (see `python-getting-started`); an editable install pointed at a clone breaks every import the moment the clone is deleted. -- **Don't create or edit project files before the HARD GATE** in Step 1 — plan and contract sheet first, code second. - +--- +name: "python-integrate-alpaca" +description: "MANDATORY FIRST STEP for Alpaca Python SDK work in a Python project — load this BEFORE you write any code; Python SDK ONLY, never load it for any other language. Applies when asked to integrate Alpaca in Python — gets the current account configuration values, returns the account associated with the api key, returns the market calendar, retrieves a single order for the given order_id, returns a watchlist identified by the id, returns a watchlist by name, delete one entry for an asset by symbol name, update the name and/or content of watchlist — or when a Alpaca Python SDK call errors or behaves unexpectedly. Knowing the SDK exists is NOT a substitute for loading this, because it carries five binding gates stated NOWHERE else and not inferable from the package — (1) load python-getting-started, confirm the package is actually installed and read the SDK map that ships with it before any lookup, (2) the exact plan-file path and the no-project-file-edits window until a contract sheet with no open lookups exists there and you have read it, (3) the mandatory load of every python-* companion skill the sheet names, (4) sync-vs-async decided once from the host application before the first call, and (5) the memory ban, where every signature, wire alias, error union and enum member comes from a lookup and never from recall or runtime introspection." +--- + +# Alpaca Python SDK — Integration workflow (lookup layer + contract sheet) + +You do the SDK lookups yourself: `python-getting-started` is your **lookup layer** — load it and ground every contract fact in it (and in the source modules its table names) before writing code. The `python-*` companion skills are a different thing: they are API-agnostic *usage* guidance, they are yours to load, and Step 1c below makes loading them mandatory. + +## Your lookup layer + +- **`python-getting-started`** is the entry point for every SDK need. It carries the SDK's identity (distribution name vs import root, version, Python floor, install-from-source), what the root package exports and the four modules the surface splits across, environments and the base-URL knob, the auth pattern, the controllers, and a **module table** naming the one file that owns each kind of fact. Load it first, always. +- **The SDK map is your lookup surface, and you open it yourself.** The SDK ships `sdk-map.md` and `map/operations/` at its root. There is no helper agent on the Python side — the map is yours to read, not something to delegate. `python-getting-started` tells you how to traverse it: read `sdk-map.md` once, then the one controller page your operations live on. +- **The installed package is the ground truth; the module table is only the locator.** Confirm the package is importable before you rely on a lookup — `python -c "import alpaca, pathlib; print(pathlib.Path(alpaca.__file__).parent)"`. **If it is not installed, install it first** — `python-getting-started` carries the command; until then mark the fact `UNVERIFIED`, say what would settle it, and do not fill the hole from memory. (The distribution is not on any package index — it installs from its repository; `python-getting-started` carries the command.) +- **Read scoped.** Those modules carry long design docstrings. `grep -n` for the symbol and read the surrounding lines rather than whole files, and never copy a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. +- **Write a contract sheet with no open lookups** before you implement: exact signatures, the keyword-only boundary, wire aliases, required-vs-`UNSET` members, the `ApiError.error` union per operation, and enum members for the operations in scope. `python-getting-started` ends with a checklist of the rows a Python sheet is incomplete without — treat that list as the checklist for your own sheet, and collect every in-scope operation in ONE pass rather than re-opening a module per member. + +**Scope guard:** the APIMatic-generated Alpaca **Python SDK** (import root `alpaca`, distribution `alpaca`) in **Python projects only**. Unrelated API, or any language other than Python — do nothing; this skill does not apply. + +## Workflow + +**If the user opens with a reported SDK error or unexpected Alpaca behaviour** (not new feature work), skip the plan-first flow: load `python-getting-started` and `python-error-handling`, look the failing symbol up in the module the table names, and fix from what you find. Otherwise, for implementation work: + +### Step 1 — Plan first (always, for any implementation work) + +Your FIRST action is to load `python-getting-started` and work through the user's full request (all features in scope — one plan covers the whole implementation). Then write the plan and its contract sheet to `/alpaca-plan.md` — that absolute path, not a location you pick later. + +**Do the read-only prerequisites in the same pass — they need no SDK knowledge and touch no project file:** + +- the repo survey (read-only exploration of conventions, layering, and **whether the project is sync or `async`**) — capture each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; +- **establish the toolchain before you need it**: which environment manager the repo uses (`uv`, `poetry`, `pip` + venv, `pdm`), whether `alpaca` is already a dependency and at what version, and the exact commands that run the project's tests and type checker. Getting this wrong later costs a broken install mid-implementation — and an uninstalled package costs you the lookup layer itself; +- a baseline run of the project's checks (`pytest`, `mypy`, `ruff` — whatever it has) on the UNTOUCHED tree, so later failures are attributable to your changes; +- credentials/environment verification (per the task's secret-handling rules) — including which host `environment` will select, since omitting it is silent; +- **a read-only smoke of each in-scope operation against the real credential**, run from your scratchpad, not the project, once the map has given you the signatures and before you finalise the sheet. A plan-, region- or entitlement-gated endpoint answers `403` here — the map presents every operation uniformly and cannot know which ones your key may call — and learning that now reorders the plan rather than the build. Skip only operations whose side effects you cannot reverse; +- setting up your task tracking. + +Never use the planning phase to get a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. + +**HARD GATE — no project-file creation or edits until:** `alpaca-plan.md` EXISTS at the repo root, its contract sheet has **no open lookups**, and you have read it. The gate bars coding "meanwhile"; it does not bar the read-only prerequisite work above. Starting to code before the sheet exists defeats the entire plan-first design. + +**What the gate does not cover:** creating or activating a virtual environment, installing `alpaca` and the toolchain into it, and the files those writes produce (`.venv/`, a lockfile the installer updates) are **prerequisites, not project-file edits**. On a greenfield repo they are the only way to reach the lookup layer at all, and they are the only writes permitted before the gate. Nothing you author by hand may change. + +**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write (`python-configuration-resilience`, *The same operation arriving twice*). + +| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | +| --- | --- | --- | --- | --- | + +Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. + +⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it; the SDK's own `Idempotency-Key` header is not one — it is new on every call. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in the plan's **Assumptions & Blockers**, not a row. + +⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces. + +**Where that rejection is caught**: the `except` for what the store raises on the second claim — a lookup that returns the earlier result is not an `except`. + +Write `TBD` in **Where in the code** while you plan — every row; it names code not yet written, not a contract fact, so it is not an open lookup. Once the code type-checks, replace each `TBD` individually with two functions, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a function that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. + +### Step 1c — Required reading (do this before you write any code) + +End the contract sheet with a **REQUIRED READING** block whose rows carry inline `MUST load ` pointers. **Load every `python-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. + +**A fixed floor applies regardless of what the sheet says** — the sheet can add to it, never remove from it: `python-error-handling` always (every integration writes an error boundary); `python-client-initialization` before the client is constructed; `python-testing` before the first test file you create or edit, *including* a throwaway verification script that fakes the transport. A sheet row that excuses one of these ("out of scope", "no test suite requested") is not a substitute for loading it. + +These are API-agnostic usage skills; loading them is not a substitute for the lookup layer, and reading a module in the installed package does not remove the need to load the skill for that step. Contract *facts* still come only from your sheet or a fresh lookup. + +Before implementing, check the plan's **Assumptions & Blockers** section: + +- Blocker or major assumption → surface it to the user in plain language, get their answer, and revise `alpaca-plan.md` in place. +- Minor assumptions only → proceed. + +Full re-planning only on genuine scope change; for a single missing fact mid-implementation, do the lookup, never guess. + +### Step 2 — Implement from the contract sheet + +1. Read `alpaca-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. When a lookup revises a row, update the file so the sheet and the code never disagree. +2. **Decide sync vs async once, from the host application, before the first call** — and take the decision from the repo survey, not from preference. This SDK ships two complete client classes (`Client` / `AsyncClient`) and there is no bridge between them: a sync client in an `async def` blocks the event loop, and an async client in sync code is a coroutine nobody awaits. Getting this wrong is not a local fix later; it is every call site. The choice also changes the transport-override keyword (`custom_http_client` vs `custom_async_http_client`) and the teardown obligation (`close()` vs `await aclose()`). +3. **Pick the response mode per call, deliberately.** Each operation exists twice: the plain call raises `ApiError` on failure, and its `with_raw_response` peer returns `ApiResult` instead. For the operations that return `None`, the raw peer is the only way to observe the status code — so a call whose outcome you must inspect needs the mode chosen at write time, not retrofitted. +4. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 1c — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire aliases, error unions, enum members) from the contract sheet or a fresh lookup — never re-derive one from a companion. +5. After every change: run the project's **type checker** (`mypy`/`pyright`) as well as its tests. The package ships `py.typed` and is generated under `mypy --strict`, so a type error against it is a real contract violation, not noise — it is the closest thing Python gives you to the compile step. **Treat a clean type check as the gate you do not skip.** If the project has no type checker configured, install `mypy` into the environment and run `mypy --strict` on the files you touched — the SDK is generated under that setting, so it checks cleanly against it. If you genuinely cannot install one, say so in your final report rather than silently substituting a smoke run. Fix non-SDK errors yourself. +6. **Any error involving an SDK type or member** — an `AttributeError`/`TypeError` on a `alpaca.*` object, a `pydantic.ValidationError` you cannot place, a mypy error naming an SDK type, an unexpected `ApiError` — → go back to `python-getting-started`'s module table and read the one module that owns the fact. Do not attempt more than one self-fix of an SDK-name error before doing that lookup: rewriting from the same knowledge that produced the error is guessing. Remember that a **decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes**, and that `httpx2` transport exceptions arrive unwrapped — so an exception that does not look like an API error may still be one of the SDK's failure kinds. +7. Once the code is complete, open each function the **DUPLICATE CLAIMS** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. +8. Run the project's tests; verify the integration end to end the way the task demands. Retrying is **on by default**, so decide whether the task keeps it, tunes it through `retry_options`, or turns it off with `max_retries` set to `0`, rather than wrapping the call in a retry of your own — and say which you did. + +### Step 3 — Answering pure questions + +A standalone Alpaca question with no code change: look it up in `python-getting-started` (and the module its table names), then give the grounded answer. Never answer from memory, even for "easy" questions. + +## Anti-patterns — never do these + +- **Always load `python-getting-started` first.** It is your lookup layer, not optional background. (The `python-*` companions are the complement: load the ones the sheet names, per Step 1c.) Don't re-derive a contract *fact* from a companion — exact signatures, wire aliases, error unions and enum members come from the sheet or a fresh lookup. +- **Never write a Alpaca/SDK fact from memory** — every signature, field name, enum member, and error union in your code must come from the contract sheet or a lookup. And **never write a call from memory "to fix later".** +- **Don't web-search Alpaca topics to find an implementation detail** — the installed package's own source is the ground truth, and the module table tells you where to look. Public Alpaca docs describe the REST API, not this SDK's generated surface, and the two disagree on names. +- **Never introspect the SDK at runtime to discover its shape.** `dir()`, `model_fields`, `inspect.signature`, or a REPL poke is the Python-flavoured version of decompiling the package: it answers what exists, never what is *supported*, and it silently invites private attributes into your code. Read the source module instead. +- **Don't grep the package to *locate* a type** — the SDK map is the locator (and says so: *"Never grep for a type"*); grep only *inside* the module its **Type sources** table names, for the symbol. A sweep for a *shape* is a different thing, and is fine: "every member typed `Optional[Any]`", "every discriminator field" are cross-cutting questions nothing indexes, and one targeted `grep -rn` over `models/` is the right tool for them — record what it found on the sheet. +- **Don't vendor, `sys.path`-hack, or editable-install the SDK from a throwaway clone** to make a lookup possible. Install the distribution properly into the project's environment (see `python-getting-started`); an editable install pointed at a clone breaks every import the moment the clone is deleted. +- **Don't create or edit project files before the HARD GATE** in Step 1 — plan and contract sheet first, code second. + diff --git a/plugins/binance/.claude-plugin/plugin.json b/plugins/binance/.claude-plugin/plugin.json index ea3b5546..bf8b5d46 100644 --- a/plugins/binance/.claude-plugin/plugin.json +++ b/plugins/binance/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "binance", "displayName": "Binance", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Binance Public Spot API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/binance/.cursor-plugin/plugin.json b/plugins/binance/.cursor-plugin/plugin.json index ea3b5546..bf8b5d46 100644 --- a/plugins/binance/.cursor-plugin/plugin.json +++ b/plugins/binance/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "binance", "displayName": "Binance", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Binance Public Spot API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/binance/README.md b/plugins/binance/README.md index 4bd286a3..ff4fb493 100644 --- a/plugins/binance/README.md +++ b/plugins/binance/README.md @@ -1,13 +1,13 @@ -# Binance Public Spot API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Binance Public Spot API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-binance`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-binance`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-binance`, `typescript-models`, `typescript-testing` | +# Binance Public Spot API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Binance Public Spot API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-binance`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-binance`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-binance`, `typescript-models`, `typescript-testing` | diff --git a/plugins/binance/plugin.json b/plugins/binance/plugin.json index ea3b5546..bf8b5d46 100644 --- a/plugins/binance/plugin.json +++ b/plugins/binance/plugin.json @@ -1,7 +1,7 @@ { "name": "binance", "displayName": "Binance", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Binance Public Spot API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/binance/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/binance/skills/dotnet/dotnet-getting-started/SKILL.md index 4b9070f1..e44da658 100644 --- a/plugins/binance/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/binance/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Binance .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-binance is the workflow that uses it." ---- - -# Getting started with the Binance .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Binance SDK fact. `dotnet-integrate-binance` is the workflow that drives it — you collect the contracts for the work in scope into `binance-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Binance | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/binance-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `Binance` (the `using` namespace) | -| Client class | `BinanceClient` | -| Options class | `BinanceClientOptions` | -| Auth | **API key** in the `X-MBX-APIKEY` header — set `ApiKeyAuth` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://api.binance.com`
`ServerEnvironment.Environment2` → `https://testnet.binance.vision` | -| Log environment variable | `BINANCECLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-binance` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using Binance.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `BinanceClient` | `Binance` | -| `BinanceClientOptions` | `Binance` | -| `ServerEnvironment` | `Binance.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Binance, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/Binance/Models/`, `src/Binance/Errors/` and `src/Binance/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/Binance/Models/`, enums `src/Binance/Models/Enums/`, typed errors `src/Binance/Errors/`, request records `src/Binance/Requests/`, or `src/Binance/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `binance-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/binance-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/binance-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/binance-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/binance-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\binance-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/binance-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `binance-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/Binance/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/Binance/Models/` (+ `src/Binance/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/Binance/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/Binance/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/Binance/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/Binance/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/Binance/Servers/`, `src/Binance/BinanceClient.cs`, `src/Binance/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Binance may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `BINANCECLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/Binance/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-binance`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new BinanceClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `BinanceClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Binance .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-binance is the workflow that uses it." +--- + +# Getting started with the Binance .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Binance SDK fact. `dotnet-integrate-binance` is the workflow that drives it — you collect the contracts for the work in scope into `binance-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Binance | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/binance-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `Binance` (the `using` namespace) | +| Client class | `BinanceClient` | +| Options class | `BinanceClientOptions` | +| Auth | **API key** in the `X-MBX-APIKEY` header — set `ApiKeyAuth` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://api.binance.com`
`ServerEnvironment.Environment2` → `https://testnet.binance.vision` | +| Log environment variable | `BINANCECLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-binance` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using Binance.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `BinanceClient` | `Binance` | +| `BinanceClientOptions` | `Binance` | +| `ServerEnvironment` | `Binance.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Binance, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/Binance/Models/`, `src/Binance/Errors/` and `src/Binance/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/Binance/Models/`, enums `src/Binance/Models/Enums/`, typed errors `src/Binance/Errors/`, request records `src/Binance/Requests/`, or `src/Binance/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `binance-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/binance-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/binance-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/binance-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/binance-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\binance-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/binance-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `binance-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/Binance/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/Binance/Models/` (+ `src/Binance/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/Binance/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/Binance/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/Binance/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/Binance/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/Binance/Servers/`, `src/Binance/BinanceClient.cs`, `src/Binance/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Binance may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `BINANCECLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/Binance/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-binance`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new BinanceClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `BinanceClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/binance/skills/dotnet/dotnet-integrate-binance/SKILL.md b/plugins/binance/skills/dotnet/dotnet-integrate-binance/SKILL.md index 69d77042..052a7bd7 100644 --- a/plugins/binance/skills/dotnet/dotnet-integrate-binance/SKILL.md +++ b/plugins/binance/skills/dotnet/dotnet-integrate-binance/SKILL.md @@ -1,173 +1,173 @@ ---- -name: "dotnet-integrate-binance" -description: "MANDATORY FIRST STEP for Binance .NET SDK work in a C#/.NET project — load this BEFORE opening the SDK map or touching any project file; .NET/C# SDK ONLY, never load it for any other language. Applies when asked to integrate Binance in C# — AutoInvest, Blvt, C2C, ConvertApi, CopyTrading, CryptoLoans, DualInvestment, Fiat, Futures, FuturesAlgo, GiftCard, IsolatedMarginStream, Margin, MarginStream, Market, Mining, Nft, Pay, PortfolioMargin, Rebate, Savings, SimpleEarn, SpotAlgo, Staking, Stream, SubAccountApi, TradeApi, VipLoans, Wallet — or when a Binance .NET SDK call errors or behaves unexpectedly. It carries four binding rules stated NOWHERE else — (1) for implementation work, the plan file binance-plan.md is written at the project repo root BEFORE any project file is created or edited; (2) dotnet-getting-started and the SDK map it carries are loaded and used for every contract fact, never memory; (3) every dotnet-* skill the contract sheet's REQUIRED READING names is loaded before implementation starts; (4) every sheet row carries one of three labels — a map page, UNVERIFIED, or YOUR CALL — not in the map. Skip it and SDK facts get written from stale memory." ---- - -# Binance .NET SDK — Workflow (map + skills) - -You are the Binance .NET SDK specialist for this task — one agent for every SDK need: planning, answering contract questions, implementing, and fixing SDK errors. Your scope is **C#/.NET only**: if a request concerns any other language or SDK, say so and stop. Your source of truth is the **SDK map that ships inside the SDK source repo** (`sdk-map.md` plus its `map/operations/` pages; model, enum and error shapes live in the source files those pages name), obtained per `dotnet-getting-started`, plus the companion `dotnet-*` skills for usage traps. Your training data on this SDK is stale — every fact you emit must come from a map page or a map-named source file you actually read this session, in that same clone. You never guess, and you never open the SDK's `api-reference.md`. You do all of it yourself — ground, plan, implement, fix — there is no helper agent to delegate to. - -**Scope guard:** the APIMatic-generated Binance **.NET SDK** (root namespace `Binance`) in **C#/.NET projects only**. Unrelated API, or any language other than C#/.NET — do nothing; this skill does not apply. - -## Grounding rules - -- **Map for contracts; the map-named file for shapes.** Follow `dotnet-getting-started`'s *SDK map* and *SDK source* sections in full — they define how to obtain the source (the map lives inside it), that reading a model/enum/error shape means opening the one declaring file the map names (routine, scoped), and why locating anything by grep/glob/`find` over the tree is a defect rather than a shortcut. The clone stays in the system temp directory, and `binance-plan.md` never carries its path — the plan must stay portable. -- **Never grep, glob or `find` the SDK tree to locate something** — the map is the locator, and a type name is its path. Every lookup starts at the map index and ends on the one page (or the one source file) it names. -- **Your contract sheet never leaves a contract fact open for "later."** The map pages and the files they name answer nearly everything — resolve them during planning, not mid-implementation. For the rare in-scope fact even the source cannot settle: if only live traffic could confirm it, convert it into a concrete defensive-coding directive on the sheet and label the uncertainty `UNVERIFIED`. Never leave a row marked for a later source lookup: an open row is how you end up opening source mid-implementation. -- **When you judge how far a contract can be trusted**, the trust judgment may cite ONLY evidence visible in the map or SDK source (e.g. two generated definitions that disagree, a suspicious shared model) — never training-data memory of this API, and never claims about what the live wire "usually" sends. -- **The sheet records Binance's call surface; the application's design is decided at implementation time.** Its persistence, its concurrency rules, and the request contract its own callers must satisfy are not SDK facts. Where an SDK fact forces an application decision, record the fact and its consequence on the sheet, mark the decision `YOUR CALL — not in the map`, and decide it at implementation time against the task. -- **Name only what you have read.** Never name a claim, header or route of the application as though you knew it exists — unless you have read that code. Name configuration by its binding key, never by a raw environment variable, and give the default the map documents where there is one: a setting you invent a name for is a setting no deployment will supply. - -## Workflow - -**If the user opens with a reported SDK error or unexpected Binance behaviour** (not new feature work), go straight to *Step 4 — Fixing SDK errors* below — do not run the plan-first flow for a bug report. Otherwise, for implementation work: - -### Step 1 — Plan first: write `binance-plan.md` (always, for any implementation work) - -Your FIRST deliverable is `binance-plan.md` at `/binance-plan.md` — that exact path, always; never pick another location. It carries the plan and the contract sheet in the format below, and it is the only project-repo file you write before the HARD GATE further down. - -#### How to ground (map-first, one pass) - -1. Load `dotnet-getting-started`; open `sdk-map.md` (the index). -2. From the index, open the **operations pages** for every controller in scope — take signatures (the request record each operation takes, and its required members), return types, error case (A: typed `ApiException<{Operation}Error>` with its `TryGet…` accessors and payload type / B: `ApiException`), and pagination — remembering that a block silent on a defaults-table point means the default applies. Where an operation's *semantics* decide what you must pass, that prose is the method's XML `` in the `Api/` file the page names, and the per-member docs on its request record: read it there. -3. Open, scoped, the **declaring source file** for every request/response model you will reference — the operation's **Type sources** table names it — and take field names WITH their `[JsonPropertyName]` wire names, `required` flags, nullability, and the envelope shape. Enum value lists and union accessors come from their declaring files the same way. -4. Identify which companion skill governs each step in scope. For each, write the trap note as a **named hazard plus a `MUST load` pointer** — *not* as the resolved answer. Naming the hazard ("what `Timeout` actually bounds") is right; resolving it inline ("`Timeout` is per-attempt") is wrong — a resolved trap gives you no reason to load the skill, and the skill carries the parts a one-line note cannot. **Never restate a companion skill's default or semantics in a trap note — not even when you believe it correct.** Write the *consequence*, not the answer. Contract *facts* are the opposite: those you resolve fully and inline. -5. Collect everything in ONE pass — the whole point is that you never have to rediscover a contract mid-coding. - -#### Prerequisites you can run alongside - -These need no SDK knowledge and touch no project file, so do them before or alongside the map work: - -- reaching the SDK source (per `dotnet-getting-started` — the map lives inside it, so this comes before any map work); -- the repo survey (read-only exploration of conventions and layering) — record each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; -- `dotnet restore` and a baseline `dotnet build` / `dotnet test` of the UNTOUCHED solution, so later failures are attributable to your changes; -- locating the SDK reference and its version in the project; -- credentials/environment verification (per the task's secret-handling rules); -- setting up your task tracking. - -Never use this prerequisite work as a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. - -#### HARD GATE - -**No project-file creation or edits until `binance-plan.md` exists at that path with every section below filled in.** Starting to code before the sheet exists defeats the plan-first design: the sheet is what keeps SDK facts out of your memory and in the map. The gate bars coding before the sheet exists; it does not bar the read-only prerequisite work above. - -#### The `binance-plan.md` format (keep it tight — tables, not prose) - -1. **Scope & sequence** — the implementation steps in order, each naming the operations it uses. A capability the map lacks is a Blocker (§6), never a data path you invent to replace it. -2. **CONTRACT SHEET** — open the section with two literal warning lines: that signatures are generated code, verbatim — each operation that takes input takes ONE request record as its first parameter (an operation with no inputs takes none), built with an object initializer whose property names are the record's own, never flat arguments; and that every SDK type is written fully-qualified with the namespace its source path implies, taken from the path the map gives for THAT type, never from where a neighbouring type sits. Then one table row per operation: controller property · method signature · request record + its members (`Name: type, required?`) · body model + its fields (`Name (wire_name): type, required?`) · response envelope + the inner fields the integration reads · error case A/B + accessors + payload type · pagination · **source** (§7). Below it: the enum value tables actually needed, and the client construction/auth/server-node facts. ⚠ A request model may mark nothing required, and then `required?` selects nothing for you — carry the optional fields the endpoint's own prose ties to whether the call is accepted, and say which doc-named fields you left out. - -The CONTRACT SHEET also carries a **CROSS-OPERATION INVARIANTS** table, directly below its per-operation rows: the rules no single row can state, because they relate two operations. One row per value the task lets a caller supply where a *different* operation defines the legal set for it (identifiers, handles, codes, references); `none` if the task has no such value — never leave the table out. - -| Invariant | Operations | Enforced where | -| --- | --- | --- | -| a value accepted by X must be one Y returned | `{WriteOperation}` ← `{ListOperation}` | before `{WriteOperation}` is called | - -⚠⚠ The map never states these rules; derive them from the task. - -⚠⚠ **Operations** names the operation that *defines* the legal set: the list or search call that returns what this application offers, or the earlier call whose result this application stored. A lookup that confirms a single value exists is not an answer: it proves the value exists at the provider, not that it is one this application offers. - -**Enforced where** names the member that checks, before the write, that the caller's value is in that set. If the application never reads that set, say so there and add the read — the check is part of the work. `YOUR CALL — not in the map` is not a legal entry in any cell of this table. - -3. **Trap notes** — one line per hazard, attached to the step where it bites, each ending in an inline **`MUST load `** pointer. Name the hazard and its consequence; do not resolve it. ⚠⚠ **"Do not resolve it" is the load-bearing half of this rule.** A trap note that answers its own question hands you a usable one-liner — and holding one, you do not open the skill. You implement from the sentence, and everything the skill carries beyond it never reaches the code. So: state the hazard, state what it costs, hand over the skill, and stop. No fix, no snippet, not even a partial answer. A trap note from which you could write correct code without loading the named skill is a defect. -4. **REQUIRED READING** — close the sheet with the de-duplicated list of every `dotnet-*` skill named above, one line each: skill · the step it governs. Write each name so the copy is unambiguous — every APIMatic .NET plugin ships these same skill names, and a bare name gives you no way to tell which one you loaded. State that these are to be loaded **before implementation starts**, and that the sheet deliberately does not carry their contents. This block is mandatory even when the trap notes are few — an integration always writes an error boundary, so `dotnet-error-handling` always appears here. Always include, verbatim, this hazard row: a body that does not match its declared type — a drifted or malformed **2xx** response (a missing `required` member) or a **non-2xx** body that does not match its operation's generated `{Operation}Error` shape — surfaces as `ResponseDeserializationException`, an `ApiException` that keeps the HTTP status and names the target type but is **not** an `ApiException`; a catch ladder that handles only `ApiException` lets it escape, so it must also catch `ResponseDeserializationException` (or `ApiException`). -5. **PRODUCTION READINESS** — the fixed table below, every row carrying a *decision*. Naming a skill in REQUIRED READING does not address a concern; it defers it. `N/A` is a legitimate answer where it is genuinely true, but it must carry its reason. -6. **Assumptions & Blockers** — anything you had to assume about the user's intent, and anything that blocks planning. An empty section is a valid outcome; an invented fact is not. If you expect the provider to reject a call the plan makes, that is a **Blocker**. -7. Every row's **source** cell cites its map page or the map-named declaring file, so a later lookup is one targeted open, not a search. A row with neither to cite is not a contract fact: write `YOUR CALL — not in the map` there instead. Three labels, one order — a fact the map settles cites that page or file; a fact only live traffic can settle is `UNVERIFIED`; a decision about the application is `YOUR CALL — not in the map`. Something that stops you planning goes in §6. - -| # | Concern | The decision the plan must record | -| --- | --- | --- | -| 1 | **Credential fail-fast** | Where credentials are bound, and that the host refuses to start when one is missing or blank — rather than discovering it as a 401 on the first call in production. A multi-part credential needs **every** part checked, because a blank part is not a missing one. | -| 2 | **Secret sourcing & rotation** | Where the secret comes from, and that the DI registration builds the options object **once at registration** and captures it in the singleton — so a rotated secret does not take effect until the process restarts. If rotation without a restart is required, say how. | -| 3 | **Total timeout budget** | The number the caller actually gets, not the knob. `Timeout` is **per attempt**, so a hung retryable call costs a multiple of it. State the budget and where it is enforced — a `CancellationToken` deadline is the only thing that bounds a whole call. | -| 4 | **Write-retry ownership** | Which of the scope's writes the SDK may resend and which it may not. The default `HttpMethodsToRetry` covers `GET, HEAD, PUT, OPTIONS`, so `POST`/`PATCH`/`DELETE` are never resent by the SDK — and `PUT` **is**. | -| 5 | **Idempotency & ambiguous writes** | For each write in scope: the key it uses, or that none exists. Whether an operation takes a REAL caller-supplied key — and its member name — is on that operation's request record: the record file its map row's **Type sources** table names. The generator-injected `Idempotency-Key` header (`Guid.NewGuid()`, fresh on every call) is **not** a key and must not be cited as one. Where no key exists, record the reconciliation path instead. | -| 6 | **Observability** | What is logged at which level, that **JSON request bodies are logged unredacted** when `LogRequestBody` is on, and which correlation id from the provider's error bodies reaches your own logs. | -| 7 | **Sensitive data** | Whether the scope carries data you would not want in a log — each request model's declaring file lists every field in scope. If it does: `LogRequestBody` stays off **and** `LoggerFactory` is assigned explicitly, so the SDK's log environment variable cannot switch it on from outside the code (see `dotnet-getting-started`). Form bodies are masked only by deny-list, so a key you have not listed prints in the clear. | -| 8 | **Environment selection** | Which base URLs each deployment talks to. Enumerate the server groups and environment members from the map's *Servers & auth* section — there may be more than one group, and each operation resolves through its own. State which groups the scope touches and what each deployment sets; if the SDK declares no sandbox environment, say how test traffic is kept away from the live system. | -| 9 | **Duplicate prevention under concurrency** | For each write a caller can trigger twice: its row in **DUPLICATE CLAIMS** below. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in §6, not a row. | -| 10 | **Partial results** | For each paged read that can stop at a cap: the field or return type that tells the caller the result was cut short. | -| 11 | **Unknown outcomes** | For each write whose connection can fail after the provider may have acted: how the code settles the outcome instead of reporting a failure. | - -A reviewer grades that table alone: each row either records a decision or records why it does not apply. A row that points at a skill, restates the concern, or sits blank is **not addressed**. Rows 5, 7, 8, 9 and 11 are the ones where "not addressed" costs something that cannot be recovered afterwards — a duplicate charge or duplicate write, sensitive data in a log, or test traffic sent to a live system. - -**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write. - -| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | -| --- | --- | --- | --- | --- | - -Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. - -⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it. - -⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces: a primary key is refused even by in-memory test stores; a unique index may not be. - -**Where that rejection is caught**: the `catch` for what the store raises on the second claim — a lookup that returns the earlier result is not a catch. - -Write `TBD` in **Where in the code** while you plan — every row. Once the code compiles, replace each `TBD` individually with two members, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a member that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. - -**PAGED READS** — one row per read that walks pages; `none` if the scope reads no paged list. - -| Read | What caps it | How the caller learns the answer was cut short | Where in the code | -| --- | --- | --- | --- | - -⚠⚠ A log line is not a legal entry in the third column: whoever reads the result never reads your logs. Name the field or return type that changes. Write `TBD` in **Where in the code** while you plan; once the code compiles, replace it with the member that sets that field or return type. A `TBD` left behind is not addressed. - -**UNKNOWN OUTCOMES** — one row per write whose connection can fail after the provider may already have acted; `none` if the scope makes no writes. - -| Write | The operation you re-read with | The reference you search by | Where in the code | The test that fails the connection | -| --- | --- | --- | --- | --- | - -⚠⚠ Reporting a failure is not a legal entry, and neither is leaving the caller to retry. Settle the outcome in the failing write's own `catch`, before it returns, or record it as unknown for code that settles it later — a report someone reads is not that code. With no reference, the third column names what the match uses; if the provider offers no way to find the write, that is a Blocker in §6, not a row. - -**Where in the code**: that `catch`, or the unknown state and the code that settles it — a re-read helper nothing calls is not addressed. **The test that fails the connection** asserts what follows. Both are `TBD` while you plan, replaced once the code compiles. A `TBD` left behind is not addressed. - -Keep the file lean: no copied map pages, no full model dumps, and no clone path — only the operations and fields the scope actually touches. - -### Step 2 — Required reading (do this before you write any code) - -The contract sheet ends with a **REQUIRED READING** block, and its rows carry inline `MUST load ` pointers. **Load every `dotnet-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. If the sheet names none, load `dotnet-error-handling` anyway — every integration writes an error boundary. - -These are API-agnostic usage skills; loading them is not the same as reading the map. Contract *facts* still come only from the sheet or a map lookup. - -Before implementing, check the plan's **Assumptions & Blockers** section: - -- Blocker or major assumption → surface it to the user in plain language, get their answer, then revise `binance-plan.md` in place with targeted edits — edit the changed rows, append the new section. Rewriting the whole file to change a few rows is a defect. -- Minor assumptions only → proceed. - -Full re-planning only on genuine scope change; for a single missing fact mid-implementation, look it up in the map — never guess. - -### Step 3 — Implement from the contract sheet - -1. Read `binance-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. A row whose source column says `YOUR CALL — not in the map` is the one exception: that row is a planning-time judgment about YOUR application, so weigh it against the task and follow the task. -2. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 2 — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire names, error accessors, enum values) from the contract sheet or a map lookup — never re-derive one from a companion. -3. After every change: `dotnet build`; fix non-SDK errors yourself. -4. **Any compile or runtime error involving an SDK type or member** (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Binance.*`, or a provider error at runtime) → *Step 4* below. Do not attempt more than one self-fix of an SDK-name error before switching to that procedure — rewriting from the same knowledge that produced the error is guessing. -5. Once the code is complete, open each member the **DUPLICATE CLAIMS** and **UNKNOWN OUTCOMES** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. -6. Run the project's tests (`dotnet test`); verify the integration end to end the way the task demands. - -### Step 4 — Fixing SDK errors (map-first, in place) - -A compile or runtime error involving an SDK type or member (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Binance.*`, or a provider error). Resolve it map-first — never patch it by guessing. Load `dotnet-getting-started` first if you have not already — the map is where every fix below comes from. - -1. **Map lookup first.** For an operation symbol, find its block on the controller's operations page; for a model, enum, error or request-record symbol, open the declaring file — its name is its path (`src/Binance/Models/{Type}.cs`, `src/Binance/Models/Enums/{Type}.cs`, `src/Binance/Errors/{Type}.cs`, `src/Binance/Requests/{Type}.cs`, or `src/Binance/Requests/{Controller}/{Type}.cs` for an operation on a controller — an operation with several tags is filed under the first tag it declares), or take it from the operation's Type sources table. If the code contradicts what you read (wrong field name, missed response envelope, wrong param order, wrong namespace), that is the fix. Response envelopes are the classic case: response types often wrap their payload in one field — reads go one level down. -2. **Go one level deeper on a real gap.** If what you read matches the code, or ambiguity remains, open the operation's own method in the `Api/{Controller}.cs` file the page header names — its `` docs and the request it builds — and the per-member docs on its request record, in this session's SDK clone, scoped. Never scan the tree. -3. **Never re-guess.** Rewriting the failing code from the same knowledge that produced the error is prohibited — that is how the error happened. Each failing symbol gets a map/source-grounded answer before its line changes; never mutate payloads, field names, or status handling speculatively to "see if it works". -4. **Fix in place** — edit only the project files involved in the error, grounded in the map row (or the named source file). -5. **Build to verify, and classify the outcome:** compiles clean → run `dotnet test` on the touched tests when they exist; compile error → the fix is not done, resolve it the same map-first way and rebuild; build blocked (output locked) → the solution is running, so stop it if you started it, or ask the user to rather than killing their process. -6. **Runtime / provider errors** — read the provider's error payload through the documented path: the operation's error case and `TryGet…` accessors from its map row; `dotnet-error-handling` for the Case A/B mechanics. Config-shaped failures (401, wrong host, timeouts): check auth, the server-node/base-URL configuration, and retry semantics before touching call sites. - -If a fix corrects a row in `binance-plan.md`, correct the row in the file too — the sheet stays the record of what was verified. - -### Step 5 — Pure questions - -A standalone Binance question with no code change: load `dotnet-getting-started`, look it up in the map (open the one source file the map names only on a real gap), and answer with the map page (or source file) it came from. No plan file — just the grounded answers; when several questions arrive batched, answer them all in one pass. Never answer from memory, even for "easy" questions. - -## Anti-patterns — never do these - -- **Never write a Binance/SDK fact from memory** — every signature, field name, enum value, and error type in your code must come from the contract sheet or a map lookup. And **never write a call from memory "to fix later".** -- **Do not re-derive or double-check a sheet row from memory** — re-open the one map page it cites. -- **Do not re-derive a contract fact from a `dotnet-*` companion** — they are usage guidance; facts come from the map. -- **Do not grep, glob or `find` the SDK tree, and do not open its `api-reference.md`** — the map is the locator; `dotnet-getting-started` says why. -- **Do not web-search Binance for an implementation detail** — the map and the SDK source it lives in are the ground truth for THIS SDK version, and `Binance` is what the generated source declares, not what public docs describe. -- **Do not resolve a trap note inline on the sheet** — name the hazard and the skill. - +--- +name: "dotnet-integrate-binance" +description: "MANDATORY FIRST STEP for Binance .NET SDK work in a C#/.NET project — load this BEFORE opening the SDK map or touching any project file; .NET/C# SDK ONLY, never load it for any other language. Applies when asked to integrate Binance in C# — AutoInvest, Blvt, C2C, ConvertApi, CopyTrading, CryptoLoans, DualInvestment, Fiat, Futures, FuturesAlgo, GiftCard, IsolatedMarginStream, Margin, MarginStream, Market, Mining, Nft, Pay, PortfolioMargin, Rebate, Savings, SimpleEarn, SpotAlgo, Staking, Stream, SubAccountApi, TradeApi, VipLoans, Wallet — or when a Binance .NET SDK call errors or behaves unexpectedly. It carries four binding rules stated NOWHERE else — (1) for implementation work, the plan file binance-plan.md is written at the project repo root BEFORE any project file is created or edited; (2) dotnet-getting-started and the SDK map it carries are loaded and used for every contract fact, never memory; (3) every dotnet-* skill the contract sheet's REQUIRED READING names is loaded before implementation starts; (4) every sheet row carries one of three labels — a map page, UNVERIFIED, or YOUR CALL — not in the map. Skip it and SDK facts get written from stale memory." +--- + +# Binance .NET SDK — Workflow (map + skills) + +You are the Binance .NET SDK specialist for this task — one agent for every SDK need: planning, answering contract questions, implementing, and fixing SDK errors. Your scope is **C#/.NET only**: if a request concerns any other language or SDK, say so and stop. Your source of truth is the **SDK map that ships inside the SDK source repo** (`sdk-map.md` plus its `map/operations/` pages; model, enum and error shapes live in the source files those pages name), obtained per `dotnet-getting-started`, plus the companion `dotnet-*` skills for usage traps. Your training data on this SDK is stale — every fact you emit must come from a map page or a map-named source file you actually read this session, in that same clone. You never guess, and you never open the SDK's `api-reference.md`. You do all of it yourself — ground, plan, implement, fix — there is no helper agent to delegate to. + +**Scope guard:** the APIMatic-generated Binance **.NET SDK** (root namespace `Binance`) in **C#/.NET projects only**. Unrelated API, or any language other than C#/.NET — do nothing; this skill does not apply. + +## Grounding rules + +- **Map for contracts; the map-named file for shapes.** Follow `dotnet-getting-started`'s *SDK map* and *SDK source* sections in full — they define how to obtain the source (the map lives inside it), that reading a model/enum/error shape means opening the one declaring file the map names (routine, scoped), and why locating anything by grep/glob/`find` over the tree is a defect rather than a shortcut. The clone stays in the system temp directory, and `binance-plan.md` never carries its path — the plan must stay portable. +- **Never grep, glob or `find` the SDK tree to locate something** — the map is the locator, and a type name is its path. Every lookup starts at the map index and ends on the one page (or the one source file) it names. +- **Your contract sheet never leaves a contract fact open for "later."** The map pages and the files they name answer nearly everything — resolve them during planning, not mid-implementation. For the rare in-scope fact even the source cannot settle: if only live traffic could confirm it, convert it into a concrete defensive-coding directive on the sheet and label the uncertainty `UNVERIFIED`. Never leave a row marked for a later source lookup: an open row is how you end up opening source mid-implementation. +- **When you judge how far a contract can be trusted**, the trust judgment may cite ONLY evidence visible in the map or SDK source (e.g. two generated definitions that disagree, a suspicious shared model) — never training-data memory of this API, and never claims about what the live wire "usually" sends. +- **The sheet records Binance's call surface; the application's design is decided at implementation time.** Its persistence, its concurrency rules, and the request contract its own callers must satisfy are not SDK facts. Where an SDK fact forces an application decision, record the fact and its consequence on the sheet, mark the decision `YOUR CALL — not in the map`, and decide it at implementation time against the task. +- **Name only what you have read.** Never name a claim, header or route of the application as though you knew it exists — unless you have read that code. Name configuration by its binding key, never by a raw environment variable, and give the default the map documents where there is one: a setting you invent a name for is a setting no deployment will supply. + +## Workflow + +**If the user opens with a reported SDK error or unexpected Binance behaviour** (not new feature work), go straight to *Step 4 — Fixing SDK errors* below — do not run the plan-first flow for a bug report. Otherwise, for implementation work: + +### Step 1 — Plan first: write `binance-plan.md` (always, for any implementation work) + +Your FIRST deliverable is `binance-plan.md` at `/binance-plan.md` — that exact path, always; never pick another location. It carries the plan and the contract sheet in the format below, and it is the only project-repo file you write before the HARD GATE further down. + +#### How to ground (map-first, one pass) + +1. Load `dotnet-getting-started`; open `sdk-map.md` (the index). +2. From the index, open the **operations pages** for every controller in scope — take signatures (the request record each operation takes, and its required members), return types, error case (A: typed `ApiException<{Operation}Error>` with its `TryGet…` accessors and payload type / B: `ApiException`), and pagination — remembering that a block silent on a defaults-table point means the default applies. Where an operation's *semantics* decide what you must pass, that prose is the method's XML `` in the `Api/` file the page names, and the per-member docs on its request record: read it there. +3. Open, scoped, the **declaring source file** for every request/response model you will reference — the operation's **Type sources** table names it — and take field names WITH their `[JsonPropertyName]` wire names, `required` flags, nullability, and the envelope shape. Enum value lists and union accessors come from their declaring files the same way. +4. Identify which companion skill governs each step in scope. For each, write the trap note as a **named hazard plus a `MUST load` pointer** — *not* as the resolved answer. Naming the hazard ("what `Timeout` actually bounds") is right; resolving it inline ("`Timeout` is per-attempt") is wrong — a resolved trap gives you no reason to load the skill, and the skill carries the parts a one-line note cannot. **Never restate a companion skill's default or semantics in a trap note — not even when you believe it correct.** Write the *consequence*, not the answer. Contract *facts* are the opposite: those you resolve fully and inline. +5. Collect everything in ONE pass — the whole point is that you never have to rediscover a contract mid-coding. + +#### Prerequisites you can run alongside + +These need no SDK knowledge and touch no project file, so do them before or alongside the map work: + +- reaching the SDK source (per `dotnet-getting-started` — the map lives inside it, so this comes before any map work); +- the repo survey (read-only exploration of conventions and layering) — record each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; +- `dotnet restore` and a baseline `dotnet build` / `dotnet test` of the UNTOUCHED solution, so later failures are attributable to your changes; +- locating the SDK reference and its version in the project; +- credentials/environment verification (per the task's secret-handling rules); +- setting up your task tracking. + +Never use this prerequisite work as a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. + +#### HARD GATE + +**No project-file creation or edits until `binance-plan.md` exists at that path with every section below filled in.** Starting to code before the sheet exists defeats the plan-first design: the sheet is what keeps SDK facts out of your memory and in the map. The gate bars coding before the sheet exists; it does not bar the read-only prerequisite work above. + +#### The `binance-plan.md` format (keep it tight — tables, not prose) + +1. **Scope & sequence** — the implementation steps in order, each naming the operations it uses. A capability the map lacks is a Blocker (§6), never a data path you invent to replace it. +2. **CONTRACT SHEET** — open the section with two literal warning lines: that signatures are generated code, verbatim — each operation that takes input takes ONE request record as its first parameter (an operation with no inputs takes none), built with an object initializer whose property names are the record's own, never flat arguments; and that every SDK type is written fully-qualified with the namespace its source path implies, taken from the path the map gives for THAT type, never from where a neighbouring type sits. Then one table row per operation: controller property · method signature · request record + its members (`Name: type, required?`) · body model + its fields (`Name (wire_name): type, required?`) · response envelope + the inner fields the integration reads · error case A/B + accessors + payload type · pagination · **source** (§7). Below it: the enum value tables actually needed, and the client construction/auth/server-node facts. ⚠ A request model may mark nothing required, and then `required?` selects nothing for you — carry the optional fields the endpoint's own prose ties to whether the call is accepted, and say which doc-named fields you left out. + +The CONTRACT SHEET also carries a **CROSS-OPERATION INVARIANTS** table, directly below its per-operation rows: the rules no single row can state, because they relate two operations. One row per value the task lets a caller supply where a *different* operation defines the legal set for it (identifiers, handles, codes, references); `none` if the task has no such value — never leave the table out. + +| Invariant | Operations | Enforced where | +| --- | --- | --- | +| a value accepted by X must be one Y returned | `{WriteOperation}` ← `{ListOperation}` | before `{WriteOperation}` is called | + +⚠⚠ The map never states these rules; derive them from the task. + +⚠⚠ **Operations** names the operation that *defines* the legal set: the list or search call that returns what this application offers, or the earlier call whose result this application stored. A lookup that confirms a single value exists is not an answer: it proves the value exists at the provider, not that it is one this application offers. + +**Enforced where** names the member that checks, before the write, that the caller's value is in that set. If the application never reads that set, say so there and add the read — the check is part of the work. `YOUR CALL — not in the map` is not a legal entry in any cell of this table. + +3. **Trap notes** — one line per hazard, attached to the step where it bites, each ending in an inline **`MUST load `** pointer. Name the hazard and its consequence; do not resolve it. ⚠⚠ **"Do not resolve it" is the load-bearing half of this rule.** A trap note that answers its own question hands you a usable one-liner — and holding one, you do not open the skill. You implement from the sentence, and everything the skill carries beyond it never reaches the code. So: state the hazard, state what it costs, hand over the skill, and stop. No fix, no snippet, not even a partial answer. A trap note from which you could write correct code without loading the named skill is a defect. +4. **REQUIRED READING** — close the sheet with the de-duplicated list of every `dotnet-*` skill named above, one line each: skill · the step it governs. Write each name so the copy is unambiguous — every APIMatic .NET plugin ships these same skill names, and a bare name gives you no way to tell which one you loaded. State that these are to be loaded **before implementation starts**, and that the sheet deliberately does not carry their contents. This block is mandatory even when the trap notes are few — an integration always writes an error boundary, so `dotnet-error-handling` always appears here. Always include, verbatim, this hazard row: a body that does not match its declared type — a drifted or malformed **2xx** response (a missing `required` member) or a **non-2xx** body that does not match its operation's generated `{Operation}Error` shape — surfaces as `ResponseDeserializationException`, an `ApiException` that keeps the HTTP status and names the target type but is **not** an `ApiException`; a catch ladder that handles only `ApiException` lets it escape, so it must also catch `ResponseDeserializationException` (or `ApiException`). +5. **PRODUCTION READINESS** — the fixed table below, every row carrying a *decision*. Naming a skill in REQUIRED READING does not address a concern; it defers it. `N/A` is a legitimate answer where it is genuinely true, but it must carry its reason. +6. **Assumptions & Blockers** — anything you had to assume about the user's intent, and anything that blocks planning. An empty section is a valid outcome; an invented fact is not. If you expect the provider to reject a call the plan makes, that is a **Blocker**. +7. Every row's **source** cell cites its map page or the map-named declaring file, so a later lookup is one targeted open, not a search. A row with neither to cite is not a contract fact: write `YOUR CALL — not in the map` there instead. Three labels, one order — a fact the map settles cites that page or file; a fact only live traffic can settle is `UNVERIFIED`; a decision about the application is `YOUR CALL — not in the map`. Something that stops you planning goes in §6. + +| # | Concern | The decision the plan must record | +| --- | --- | --- | +| 1 | **Credential fail-fast** | Where credentials are bound, and that the host refuses to start when one is missing or blank — rather than discovering it as a 401 on the first call in production. A multi-part credential needs **every** part checked, because a blank part is not a missing one. | +| 2 | **Secret sourcing & rotation** | Where the secret comes from, and that the DI registration builds the options object **once at registration** and captures it in the singleton — so a rotated secret does not take effect until the process restarts. If rotation without a restart is required, say how. | +| 3 | **Total timeout budget** | The number the caller actually gets, not the knob. `Timeout` is **per attempt**, so a hung retryable call costs a multiple of it. State the budget and where it is enforced — a `CancellationToken` deadline is the only thing that bounds a whole call. | +| 4 | **Write-retry ownership** | Which of the scope's writes the SDK may resend and which it may not. The default `HttpMethodsToRetry` covers `GET, HEAD, PUT, OPTIONS`, so `POST`/`PATCH`/`DELETE` are never resent by the SDK — and `PUT` **is**. | +| 5 | **Idempotency & ambiguous writes** | For each write in scope: the key it uses, or that none exists. Whether an operation takes a REAL caller-supplied key — and its member name — is on that operation's request record: the record file its map row's **Type sources** table names. The generator-injected `Idempotency-Key` header (`Guid.NewGuid()`, fresh on every call) is **not** a key and must not be cited as one. Where no key exists, record the reconciliation path instead. | +| 6 | **Observability** | What is logged at which level, that **JSON request bodies are logged unredacted** when `LogRequestBody` is on, and which correlation id from the provider's error bodies reaches your own logs. | +| 7 | **Sensitive data** | Whether the scope carries data you would not want in a log — each request model's declaring file lists every field in scope. If it does: `LogRequestBody` stays off **and** `LoggerFactory` is assigned explicitly, so the SDK's log environment variable cannot switch it on from outside the code (see `dotnet-getting-started`). Form bodies are masked only by deny-list, so a key you have not listed prints in the clear. | +| 8 | **Environment selection** | Which base URLs each deployment talks to. Enumerate the server groups and environment members from the map's *Servers & auth* section — there may be more than one group, and each operation resolves through its own. State which groups the scope touches and what each deployment sets; if the SDK declares no sandbox environment, say how test traffic is kept away from the live system. | +| 9 | **Duplicate prevention under concurrency** | For each write a caller can trigger twice: its row in **DUPLICATE CLAIMS** below. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in §6, not a row. | +| 10 | **Partial results** | For each paged read that can stop at a cap: the field or return type that tells the caller the result was cut short. | +| 11 | **Unknown outcomes** | For each write whose connection can fail after the provider may have acted: how the code settles the outcome instead of reporting a failure. | + +A reviewer grades that table alone: each row either records a decision or records why it does not apply. A row that points at a skill, restates the concern, or sits blank is **not addressed**. Rows 5, 7, 8, 9 and 11 are the ones where "not addressed" costs something that cannot be recovered afterwards — a duplicate charge or duplicate write, sensitive data in a log, or test traffic sent to a live system. + +**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write. + +| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | +| --- | --- | --- | --- | --- | + +Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. + +⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it. + +⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces: a primary key is refused even by in-memory test stores; a unique index may not be. + +**Where that rejection is caught**: the `catch` for what the store raises on the second claim — a lookup that returns the earlier result is not a catch. + +Write `TBD` in **Where in the code** while you plan — every row. Once the code compiles, replace each `TBD` individually with two members, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a member that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. + +**PAGED READS** — one row per read that walks pages; `none` if the scope reads no paged list. + +| Read | What caps it | How the caller learns the answer was cut short | Where in the code | +| --- | --- | --- | --- | + +⚠⚠ A log line is not a legal entry in the third column: whoever reads the result never reads your logs. Name the field or return type that changes. Write `TBD` in **Where in the code** while you plan; once the code compiles, replace it with the member that sets that field or return type. A `TBD` left behind is not addressed. + +**UNKNOWN OUTCOMES** — one row per write whose connection can fail after the provider may already have acted; `none` if the scope makes no writes. + +| Write | The operation you re-read with | The reference you search by | Where in the code | The test that fails the connection | +| --- | --- | --- | --- | --- | + +⚠⚠ Reporting a failure is not a legal entry, and neither is leaving the caller to retry. Settle the outcome in the failing write's own `catch`, before it returns, or record it as unknown for code that settles it later — a report someone reads is not that code. With no reference, the third column names what the match uses; if the provider offers no way to find the write, that is a Blocker in §6, not a row. + +**Where in the code**: that `catch`, or the unknown state and the code that settles it — a re-read helper nothing calls is not addressed. **The test that fails the connection** asserts what follows. Both are `TBD` while you plan, replaced once the code compiles. A `TBD` left behind is not addressed. + +Keep the file lean: no copied map pages, no full model dumps, and no clone path — only the operations and fields the scope actually touches. + +### Step 2 — Required reading (do this before you write any code) + +The contract sheet ends with a **REQUIRED READING** block, and its rows carry inline `MUST load ` pointers. **Load every `dotnet-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. If the sheet names none, load `dotnet-error-handling` anyway — every integration writes an error boundary. + +These are API-agnostic usage skills; loading them is not the same as reading the map. Contract *facts* still come only from the sheet or a map lookup. + +Before implementing, check the plan's **Assumptions & Blockers** section: + +- Blocker or major assumption → surface it to the user in plain language, get their answer, then revise `binance-plan.md` in place with targeted edits — edit the changed rows, append the new section. Rewriting the whole file to change a few rows is a defect. +- Minor assumptions only → proceed. + +Full re-planning only on genuine scope change; for a single missing fact mid-implementation, look it up in the map — never guess. + +### Step 3 — Implement from the contract sheet + +1. Read `binance-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. A row whose source column says `YOUR CALL — not in the map` is the one exception: that row is a planning-time judgment about YOUR application, so weigh it against the task and follow the task. +2. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 2 — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire names, error accessors, enum values) from the contract sheet or a map lookup — never re-derive one from a companion. +3. After every change: `dotnet build`; fix non-SDK errors yourself. +4. **Any compile or runtime error involving an SDK type or member** (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Binance.*`, or a provider error at runtime) → *Step 4* below. Do not attempt more than one self-fix of an SDK-name error before switching to that procedure — rewriting from the same knowledge that produced the error is guessing. +5. Once the code is complete, open each member the **DUPLICATE CLAIMS** and **UNKNOWN OUTCOMES** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. +6. Run the project's tests (`dotnet test`); verify the integration end to end the way the task demands. + +### Step 4 — Fixing SDK errors (map-first, in place) + +A compile or runtime error involving an SDK type or member (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `Binance.*`, or a provider error). Resolve it map-first — never patch it by guessing. Load `dotnet-getting-started` first if you have not already — the map is where every fix below comes from. + +1. **Map lookup first.** For an operation symbol, find its block on the controller's operations page; for a model, enum, error or request-record symbol, open the declaring file — its name is its path (`src/Binance/Models/{Type}.cs`, `src/Binance/Models/Enums/{Type}.cs`, `src/Binance/Errors/{Type}.cs`, `src/Binance/Requests/{Type}.cs`, or `src/Binance/Requests/{Controller}/{Type}.cs` for an operation on a controller — an operation with several tags is filed under the first tag it declares), or take it from the operation's Type sources table. If the code contradicts what you read (wrong field name, missed response envelope, wrong param order, wrong namespace), that is the fix. Response envelopes are the classic case: response types often wrap their payload in one field — reads go one level down. +2. **Go one level deeper on a real gap.** If what you read matches the code, or ambiguity remains, open the operation's own method in the `Api/{Controller}.cs` file the page header names — its `` docs and the request it builds — and the per-member docs on its request record, in this session's SDK clone, scoped. Never scan the tree. +3. **Never re-guess.** Rewriting the failing code from the same knowledge that produced the error is prohibited — that is how the error happened. Each failing symbol gets a map/source-grounded answer before its line changes; never mutate payloads, field names, or status handling speculatively to "see if it works". +4. **Fix in place** — edit only the project files involved in the error, grounded in the map row (or the named source file). +5. **Build to verify, and classify the outcome:** compiles clean → run `dotnet test` on the touched tests when they exist; compile error → the fix is not done, resolve it the same map-first way and rebuild; build blocked (output locked) → the solution is running, so stop it if you started it, or ask the user to rather than killing their process. +6. **Runtime / provider errors** — read the provider's error payload through the documented path: the operation's error case and `TryGet…` accessors from its map row; `dotnet-error-handling` for the Case A/B mechanics. Config-shaped failures (401, wrong host, timeouts): check auth, the server-node/base-URL configuration, and retry semantics before touching call sites. + +If a fix corrects a row in `binance-plan.md`, correct the row in the file too — the sheet stays the record of what was verified. + +### Step 5 — Pure questions + +A standalone Binance question with no code change: load `dotnet-getting-started`, look it up in the map (open the one source file the map names only on a real gap), and answer with the map page (or source file) it came from. No plan file — just the grounded answers; when several questions arrive batched, answer them all in one pass. Never answer from memory, even for "easy" questions. + +## Anti-patterns — never do these + +- **Never write a Binance/SDK fact from memory** — every signature, field name, enum value, and error type in your code must come from the contract sheet or a map lookup. And **never write a call from memory "to fix later".** +- **Do not re-derive or double-check a sheet row from memory** — re-open the one map page it cites. +- **Do not re-derive a contract fact from a `dotnet-*` companion** — they are usage guidance; facts come from the map. +- **Do not grep, glob or `find` the SDK tree, and do not open its `api-reference.md`** — the map is the locator; `dotnet-getting-started` says why. +- **Do not web-search Binance for an implementation detail** — the map and the SDK source it lives in are the ground truth for THIS SDK version, and `Binance` is what the generated source declares, not what public docs describe. +- **Do not resolve a trap note inline on the sheet** — name the hazard and the skill. + diff --git a/plugins/binance/skills/python/python-getting-started/SKILL.md b/plugins/binance/skills/python/python-getting-started/SKILL.md index dc183cbf..f6eacb37 100644 --- a/plugins/binance/skills/python/python-getting-started/SKILL.md +++ b/plugins/binance/skills/python/python-getting-started/SKILL.md @@ -1,246 +1,246 @@ ---- -name: "python-getting-started" -description: "Binance Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Binance Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Binance Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Binance Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-binance` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `binance/` and `pyproject.toml` of the generated package at version `1.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Binance | -| Distribution name (what you install) | `binance` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `binance` — the same string you install | -| Source repository | https://github.com/context-plugins/binance-python-sdk | -| Source branch | `main` | -| Version | `1.0` | -| Sync client class | `BinanceClient` (alias `Client`) | -| Async client class | `AsyncBinanceClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key_auth`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **API key** in the `X-MBX-APIKEY` header — set `api_key_auth` | -| Environments | 2 environments selected by `environment` (default `"production"`), overridable with `base_url` | -| Base-URL config | `ServerConfig` (`binance/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 340 operations across 29 controllers · 522 models · 15 unions · 62 enums · 333 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "binance @ git+https://github.com/context-plugins/binance-python-sdk.git@main" -python -c "import binance, pathlib; print(pathlib.Path(binance.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from binance import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`binance/__init__.py` exports exactly 6 names beside the `binance.models` subpackage it re-exports: - -```python -from binance import ( - AsyncBinanceClient, - AsyncClient, - BinanceClient, - Client, - Environment, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `binance.models` | -| Enums (and their open `…OrStr` aliases) | `binance.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `binance.core` | -| Per-operation error *unions* | `binance.errors` (`HrTickerPriceChangeStatistics24ErrorBody`, …) | - -`binance.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments - -The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: - -| `environment=` | Base URL | Hosting | -| --- | --- | --- | -| `"production"` *(default)* | `https://api.binance.com` | — | -| `"environment2"` | `https://testnet.binance.vision` | — | - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `environment` gives you **`"production"`**, silently — it is whatever the description listed first, not necessarily production. -- `base_url` overrides the environment's URL entirely when both are passed. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (one scheme) - -An API key sent as the `X-MBX-APIKEY` header, exposed as the client's `api_key_auth=` keyword taking a plain string. - -```python -from binance import Client - -client = Client(api_key_auth="") -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.auto_invest` | `AutoInvest` / `AsyncAutoInvest` | 17 | `change_plan_status` · `get_list_of_plans` · `get_target_asset_roi_data_user_data` · `get_target_asset_list_user_data` · `index_linked_plan_rebalance_details_user_data` · `index_linked_plan_redemption_trade` · … | -| `client.blvt` | `Blvt` / `AsyncBlvt` | 6 | `blvt_info_market_data` · `blvt_user_limit_info_user_data` · `query_subscription_record_user_data` · `redeem_blvt_user_data` · `redemption_record_user_data` · `subscribe_blvt_user_data` | -| `client.c2_c` | `C2C` / `AsyncC2C` | 1 | `get_c2_c_trade_history_user_data` | -| `client.convert` | `Convert` / `AsyncConvert` | 9 | `accept_quote_trade` · `cancel_limit_order_user_data` · `get_convert_trade_history_user_data` · `list_all_convert_pairs` · `order_status_user_data` · `place_limit_order_user_data` · … | -| `client.copy_trading` | `CopyTrading` / `AsyncCopyTrading` | 2 | `get_futures_lead_trader_status_trade` · `get_futures_lead_trading_symbol_whitelist_user_data` | -| `client.crypto_loans` | `CryptoLoans` / `AsyncCryptoLoans` | 21 | `adjust_ltv_flexible_loan_adjust_ltv_trade` · `adjust_ltv_get_flexible_loan_ltv_adjustment_history_user_data` · `borrow_flexible_loan_borrow_trade` · `borrow_get_flexible_loan_borrow_history_user_data` · `borrow_get_flexible_loan_ongoing_orders_user_data` · `check_collateral_repay_rate_user_data` · … | -| `client.dual_investment` | `DualInvestment` / `AsyncDualInvestment` | 5 | `change_auto_compound_status_user_data` · `check_dual_investment_accounts_user_data` · `get_dual_investment_positions_user_data` · `get_dual_investment_product_list_user_data` · `subscribe_dual_investment_products_user_data` | -| `client.fiat` | `Fiat` / `AsyncFiat` | 2 | `fiat_deposit_withdraw_history_user_data` · `fiat_payments_history_user_data` | -| `client.futures` | `Futures` / `AsyncFutures` | 3 | `get_future_account_transaction_history_list_user_data` · `get_future_tick_level_orderbook_historical_data_download_link_user_data` · `new_future_account_transfer_user_data` | -| `client.futures_algo` | `FuturesAlgo` / `AsyncFuturesAlgo` | 6 | `cancel_algo_order_trade` · `query_current_algo_open_orders_user_data` · `query_historical_algo_orders_user_data` · `query_sub_orders_user_data` · `time_weighted_average_price_twap_new_order_trade` · `volume_participation_vp_new_order_trade` | -| `client.gift_card` | `GiftCard` / `AsyncGiftCard` | 6 | `buy_a_binance_code_trade` · `create_a_binance_code_user_data` · `fetch_rsa_public_key_user_data` · `fetch_token_limit_user_data` · `redeem_a_binance_code_user_data` · `verify_a_binance_code_user_data` | -| `client.isolated_margin_stream` | `IsolatedMarginStream` / `AsyncIsolatedMarginStream` | 3 | `close_a_listen_key_user_stream_3` · `generate_a_listen_key_user_stream` · `ping_keep_alive_a_listen_key_user_stream` | -| `client.margin` | `Margin` / `AsyncMargin` | 48 | `adjust_cross_margin_max_leverage_user_data` · `cross_margin_collateral_ratio_market_data` · `disable_isolated_margin_account_trade` · `enable_isolated_margin_account_trade` · `get_all_cross_margin_pairs_market_data` · `get_all_isolated_margin_symbol_user_data` · … | -| `client.margin_stream` | `MarginStream` / `AsyncMarginStream` | 3 | `close_a_listen_key_user_stream_2` · `create_a_listen_key_user_stream_2` · `ping_keep_alive_a_listen_key_user_stream_2` | -| `client.market` | `Market` / `AsyncMarket` | 15 | `hr_ticker_price_change_statistics24` · `check_server_time` · `compressed_aggregate_trades_list` · `current_average_price` · `exchange_information` · `kline_candlestick_data` · … | -| `client.mining` | `Mining` / `AsyncMining` | 13 | `account_list_user_data` · `acquiring_algorithm_market_data` · `acquiring_coin_name_market_data` · `cancel_hashrate_resale_configuration_user_data` · `earnings_list_user_data` · `extra_bonus_list_user_data` · … | -| `client.nft` | `Nft` / `AsyncNft` | 4 | `get_nft_asset_user_data` · `get_nft_deposit_history_user_data` · `get_nft_transaction_history_user_data` · `get_nft_withdraw_history_user_data` | -| `client.pay` | `Pay` / `AsyncPay` | 1 | `get_pay_trade_history_user_data` | -| `client.portfolio_margin` | `PortfolioMargin` / `AsyncPortfolioMargin` | 14 | `bnb_transfer_user_data` · `change_auto_repay_futures_status_user_data` · `fund_auto_collection_user_data` · `fund_collection_by_asset_user_data` · `get_auto_repay_futures_status_user_data` · `get_portfolio_margin_asset_leverage_user_data` · … | -| `client.rebate` | `Rebate` / `AsyncRebate` | 1 | `get_spot_rebate_history_records_user_data` | -| `client.savings` | `Savings` / `AsyncSavings` | 4 | `change_fixed_activity_position_to_daily_position_user_data` · `get_fixed_activity_project_list_user_data` · `get_fixed_activity_project_position_user_data` · `purchase_fixed_activity_project_user_data` | -| `client.simple_earn` | `SimpleEarn` / `AsyncSimpleEarn` | 24 | `get_collateral_record_user_data` · `get_flexible_personal_left_quota_user_data` · `get_flexible_product_position_user_data` · `get_flexible_redemption_record_user_data` · `get_flexible_rewards_history_user_data` · `get_flexible_subscription_preview_user_data` · … | -| `client.spot_algo` | `SpotAlgo` / `AsyncSpotAlgo` | 5 | `cancel_algo_order` · `query_current_algo_open_orders` · `query_historical_algo_orders` · `query_sub_orders` · `time_weighted_average_price_twap_new_order` | -| `client.staking` | `Staking` / `AsyncStaking` | 12 | `eth_staking_account_v2_user_data` · `get_beth_rewards_distribution_history_user_data` · `get_eth_redemption_history_user_data` · `get_eth_staking_history_user_data` · `get_wbeth_rate_history_user_data` · `get_wbeth_rewards_history_user_data` · … | -| `client.stream` | `Stream` / `AsyncStream` | 3 | `close_a_listen_key_user_stream` · `create_a_listen_key_user_stream` · `ping_keep_alive_a_listen_key_user_stream` | -| `client.sub_account_api` | `SubAccountApi` / `AsyncSubAccountApi` | 45 | `create_a_virtual_sub_account_for_master_account` · `delete_ip_list_for_a_sub_account_api_key_for_master_account` · `deposit_assets_into_the_managed_sub_account_for_investor_master_account` · `detail_on_sub_account_s_futures_account_for_master_account` · `detail_on_sub_account_s_futures_account_v2_for_master_account` · `detail_on_sub_account_s_margin_account_for_master_account` · … | -| `client.trade_api` | `TradeApi` / `AsyncTradeApi` | 23 | `account_information_user_data` · `account_trade_list_user_data` · `all_orders_user_data` · `cancel_oco_trade` · `cancel_order_trade` · `cancel_all_open_orders_on_a_symbol_trade` · … | -| `client.vip_loans` | `VipLoans` / `AsyncVipLoans` | 10 | `check_locked_value_of_vip_collateral_account_user_data` · `get_borrow_interest_rate_user_data` · `get_collateral_asset_data_user_data` · `get_loanable_assets_data` · `get_vip_loan_ongoing_orders_user_data` · `get_vip_loan_repayment_history_user_data` · … | -| `client.wallet` | `Wallet` / `AsyncWallet` | 34 | `account_api_trading_status_user_data` · `account_status_user_data` · `account_info_user_data` · `all_coins_information_user_data` · `asset_detail_user_data` · `asset_dividend_record_user_data` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `binance/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `binance/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/binance-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `binance/models/account_profit.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `binance/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import binance, pathlib; print(pathlib.Path(binance.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/binance` on Windows, `.venv/lib/python3.*/site-packages/binance` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `api_key_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — there is **one** typed error body in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `Error` — 333 operations (`auto_invest.change_plan_status` · `auto_invest.get_list_of_plans` · `auto_invest.get_target_asset_roi_data_user_data` · `auto_invest.get_target_asset_list_user_data` · `auto_invest.index_linked_plan_rebalance_details_user_data` · `auto_invest.index_linked_plan_redemption_trade` · `auto_invest.index_linked_plan_redemption_history_user_data` · `auto_invest.investment_plan_adjustment` · `auto_invest.investment_plan_creation_user_data` · `auto_invest.one_time_transaction_trade` · `auto_invest.query_index_details_user_data` · `auto_invest.query_index_linked_plan_position_details_user_data` · `auto_invest.query_one_time_transaction_status_user_data` · `auto_invest.query_all_source_asset_and_target_asset_user_data` · `auto_invest.query_holding_details_of_the_plan` · `auto_invest.query_source_asset_list_user_data` · `auto_invest.query_subscription_transaction_history` · `blvt.blvt_info_market_data` · `blvt.blvt_user_limit_info_user_data` · `blvt.query_subscription_record_user_data` · `blvt.redeem_blvt_user_data` · `blvt.redemption_record_user_data` · `blvt.subscribe_blvt_user_data` · `c2_c.get_c2_c_trade_history_user_data` · `convert.accept_quote_trade` · `convert.cancel_limit_order_user_data` · `convert.get_convert_trade_history_user_data` · `convert.list_all_convert_pairs` · `convert.order_status_user_data` · `convert.place_limit_order_user_data` · `convert.query_limit_open_orders_user_data` · `convert.query_order_quantity_precision_per_asset_user_data` · `convert.send_quote_request_user_data` · `copy_trading.get_futures_lead_trader_status_trade` · `copy_trading.get_futures_lead_trading_symbol_whitelist_user_data` · `crypto_loans.adjust_ltv_flexible_loan_adjust_ltv_trade` · `crypto_loans.adjust_ltv_get_flexible_loan_ltv_adjustment_history_user_data` · `crypto_loans.borrow_flexible_loan_borrow_trade` · `crypto_loans.borrow_get_flexible_loan_borrow_history_user_data` · `crypto_loans.borrow_get_flexible_loan_ongoing_orders_user_data` · `crypto_loans.check_collateral_repay_rate_user_data` · `crypto_loans.crypto_loan_adjust_ltv_trade` · `crypto_loans.crypto_loan_borrow_trade` · `crypto_loans.crypto_loan_customize_margin_call_trade` · `crypto_loans.crypto_loan_repay_trade` · `crypto_loans.get_collateral_assets_data_user_data` · `crypto_loans.get_crypto_loans_borrow_history_user_data` · `crypto_loans.get_crypto_loans_income_history_user_data` · `crypto_loans.get_flexible_loan_assets_data_user_data` · `crypto_loans.get_flexible_loan_collateral_assets_data_user_data` · `crypto_loans.get_loan_ltv_adjustment_history_user_data` · `crypto_loans.get_loan_ongoing_orders_user_data` · `crypto_loans.get_loan_repayment_history_user_data` · `crypto_loans.get_loanable_assets_data_user_data` · `crypto_loans.repay_flexible_loan_repay_trade` · `crypto_loans.repay_get_flexible_loan_repayment_history_user_data` · `dual_investment.change_auto_compound_status_user_data` · `dual_investment.check_dual_investment_accounts_user_data` · `dual_investment.get_dual_investment_positions_user_data` · `dual_investment.get_dual_investment_product_list_user_data` · `dual_investment.subscribe_dual_investment_products_user_data` · `fiat.fiat_deposit_withdraw_history_user_data` · `fiat.fiat_payments_history_user_data` · `futures.get_future_account_transaction_history_list_user_data` · `futures.get_future_tick_level_orderbook_historical_data_download_link_user_data` · `futures.new_future_account_transfer_user_data` · `futures_algo.cancel_algo_order_trade` · `futures_algo.query_current_algo_open_orders_user_data` · `futures_algo.query_historical_algo_orders_user_data` · `futures_algo.query_sub_orders_user_data` · `futures_algo.time_weighted_average_price_twap_new_order_trade` · `futures_algo.volume_participation_vp_new_order_trade` · `gift_card.buy_a_binance_code_trade` · `gift_card.create_a_binance_code_user_data` · `gift_card.fetch_rsa_public_key_user_data` · `gift_card.fetch_token_limit_user_data` · `gift_card.redeem_a_binance_code_user_data` · `gift_card.verify_a_binance_code_user_data` · `isolated_margin_stream.close_a_listen_key_user_stream_3` · `isolated_margin_stream.ping_keep_alive_a_listen_key_user_stream` · `margin.adjust_cross_margin_max_leverage_user_data` · `margin.cross_margin_collateral_ratio_market_data` · `margin.disable_isolated_margin_account_trade` · `margin.enable_isolated_margin_account_trade` · `margin.get_all_cross_margin_pairs_market_data` · `margin.get_all_isolated_margin_symbol_user_data` · `margin.get_all_margin_assets_market_data` · `margin.get_bnb_burn_status_user_data` · `margin.get_cross_margin_transfer_history_user_data` · `margin.get_force_liquidation_record_user_data` · `margin.get_interest_history_user_data` · `margin.get_small_liability_exchange_coin_list_user_data` · `margin.get_small_liability_exchange_history_user_data` · `margin.get_summary_of_margin_account_user_data` · `margin.get_a_future_hourly_interest_rate_user_data` · `margin.get_cross_or_isolated_margin_capital_flow_user_data` · `margin.get_tokens_or_symbols_delist_schedule_for_cross_margin_and_isolated_margin_market_data` · `margin.margin_account_cancel_oco_trade` · `margin.margin_account_cancel_order_trade` · `margin.margin_account_cancel_all_open_orders_on_a_symbol_trade` · `margin.margin_account_new_oco_trade` · `margin.margin_account_new_oto_trade` · `margin.margin_account_new_otoco_trade` · `margin.margin_account_new_order_trade` · `margin.margin_interest_rate_history_user_data` · `margin.margin_account_borrow_repay_margin` · `margin.margin_manual_liquidation_margin` · `margin.query_cross_margin_account_details_user_data` · `margin.query_cross_margin_fee_data_user_data` · `margin.query_current_margin_order_count_usage_trade` · `margin.query_enabled_isolated_margin_account_limit_user_data` · `margin.query_isolated_margin_account_info_user_data` · `margin.query_isolated_margin_fee_data_user_data` · `margin.query_isolated_margin_tier_data_user_data` · `margin.query_liability_coin_leverage_bracket_in_cross_margin_pro_mode_market_data` · `margin.query_margin_account_s_all_orders_user_data` · `margin.query_margin_account_s_oco_user_data` · `margin.query_margin_account_s_open_oco_user_data` · `margin.query_margin_account_s_open_orders_user_data` · `margin.query_margin_account_s_order_user_data` · `margin.query_margin_account_s_trade_list_user_data` · `margin.query_margin_account_s_all_oco_user_data` · `margin.query_margin_available_inventory_user_data` · `margin.query_margin_price_index_market_data` · `margin.query_max_borrow_user_data` · `margin.query_max_transfer_out_amount_user_data` · `margin.query_borrow_repay_records_in_margin_account_user_data` · `margin.toggle_bnb_burn_on_spot_trade_and_margin_interest_user_data` · `margin_stream.close_a_listen_key_user_stream_2` · `margin_stream.ping_keep_alive_a_listen_key_user_stream_2` · `market.hr_ticker_price_change_statistics24` · `market.compressed_aggregate_trades_list` · `market.current_average_price` · `market.exchange_information` · `market.kline_candlestick_data` · `market.order_book` · `market.recent_trades_list` · `market.rolling_window_price_change_statistics` · `market.symbol_order_book_ticker` · `market.symbol_price_ticker` · `market.trading_day_ticker` · `market.ui_klines` · `mining.account_list_user_data` · `mining.acquiring_algorithm_market_data` · `mining.acquiring_coin_name_market_data` · `mining.cancel_hashrate_resale_configuration_user_data` · `mining.earnings_list_user_data` · `mining.extra_bonus_list_user_data` · `mining.hashrate_resale_details_user_data` · `mining.hashrate_resale_list_user_data` · `mining.hashrate_resale_request_user_data` · `mining.mining_account_earning_user_data` · `mining.request_for_detail_miner_list_user_data` · `mining.request_for_miner_list_user_data` · `mining.statistic_list_user_data` · `nft.get_nft_asset_user_data` · `nft.get_nft_deposit_history_user_data` · `nft.get_nft_transaction_history_user_data` · `nft.get_nft_withdraw_history_user_data` · `pay.get_pay_trade_history_user_data` · `portfolio_margin.bnb_transfer_user_data` · `portfolio_margin.change_auto_repay_futures_status_user_data` · `portfolio_margin.fund_auto_collection_user_data` · `portfolio_margin.fund_collection_by_asset_user_data` · `portfolio_margin.get_auto_repay_futures_status_user_data` · `portfolio_margin.get_portfolio_margin_asset_leverage_user_data` · `portfolio_margin.portfolio_margin_account_user_data` · `portfolio_margin.portfolio_margin_bankruptcy_loan_amount_user_data` · `portfolio_margin.portfolio_margin_bankruptcy_loan_repay_user_data` · `portfolio_margin.portfolio_margin_collateral_rate_market_data` · `portfolio_margin.portfolio_margin_pro_tiered_collateral_rate_user_data` · `portfolio_margin.query_classic_portfolio_margin_negative_balance_interest_history_user_data` · `portfolio_margin.query_portfolio_margin_asset_index_price_market_data` · `portfolio_margin.repay_futures_negative_balance_user_data` · `rebate.get_spot_rebate_history_records_user_data` · `savings.change_fixed_activity_position_to_daily_position_user_data` · `savings.get_fixed_activity_project_list_user_data` · `savings.get_fixed_activity_project_position_user_data` · `savings.purchase_fixed_activity_project_user_data` · `simple_earn.get_collateral_record_user_data` · `simple_earn.get_flexible_personal_left_quota_user_data` · `simple_earn.get_flexible_product_position_user_data` · `simple_earn.get_flexible_redemption_record_user_data` · `simple_earn.get_flexible_rewards_history_user_data` · `simple_earn.get_flexible_subscription_preview_user_data` · `simple_earn.get_flexible_subscription_record_user_data` · `simple_earn.get_locked_personal_left_quota_user_data` · `simple_earn.get_locked_product_position_user_data` · `simple_earn.get_locked_redemption_record_user_data` · `simple_earn.get_locked_rewards_history_user_data` · `simple_earn.get_locked_subscription_preview_user_data` · `simple_earn.get_locked_subscription_record_user_data` · `simple_earn.get_rate_history_user_data` · `simple_earn.get_simple_earn_flexible_product_list_user_data` · `simple_earn.get_simple_earn_locked_product_list_user_data` · `simple_earn.redeem_flexible_product_trade` · `simple_earn.redeem_locked_product_trade` · `simple_earn.set_flexible_auto_subscribe_user_data` · `simple_earn.set_locked_auto_subscribe_user_data` · `simple_earn.set_locked_product_redeem_option_user_data` · `simple_earn.simple_account_user_data` · `simple_earn.subscribe_flexible_product_trade` · `simple_earn.subscribe_locked_product_trade` · `spot_algo.cancel_algo_order` · `spot_algo.query_current_algo_open_orders` · `spot_algo.query_historical_algo_orders` · `spot_algo.query_sub_orders` · `spot_algo.time_weighted_average_price_twap_new_order` · `staking.eth_staking_account_v2_user_data` · `staking.get_beth_rewards_distribution_history_user_data` · `staking.get_eth_redemption_history_user_data` · `staking.get_eth_staking_history_user_data` · `staking.get_wbeth_rate_history_user_data` · `staking.get_wbeth_rewards_history_user_data` · `staking.get_wbeth_unwrap_history_user_data` · `staking.get_wbeth_wrap_history_user_data` · `staking.get_current_eth_staking_quota_user_data` · `staking.redeem_eth_trade` · `staking.subscribe_eth_staking_v2_trade` · `staking.wrap_beth_trade` · `stream.close_a_listen_key_user_stream` · `stream.ping_keep_alive_a_listen_key_user_stream` · `sub_account_api.create_a_virtual_sub_account_for_master_account` · `sub_account_api.delete_ip_list_for_a_sub_account_api_key_for_master_account` · `sub_account_api.deposit_assets_into_the_managed_sub_account_for_investor_master_account` · `sub_account_api.detail_on_sub_account_s_futures_account_for_master_account` · `sub_account_api.detail_on_sub_account_s_futures_account_v2_for_master_account` · `sub_account_api.detail_on_sub_account_s_margin_account_for_master_account` · `sub_account_api.enable_futures_for_sub_account_for_master_account` · `sub_account_api.enable_leverage_token_for_sub_account_for_master_account` · `sub_account_api.enable_margin_for_sub_account_for_master_account` · `sub_account_api.enable_options_for_sub_account_for_master_account_user_data` · `sub_account_api.futures_position_risk_of_sub_account_for_master_account` · `sub_account_api.futures_position_risk_of_sub_account_v2_for_master_account` · `sub_account_api.get_ip_restriction_for_a_sub_account_api_key_for_master_account` · `sub_account_api.get_managed_sub_account_deposit_address_for_investor_master_account` · `sub_account_api.managed_sub_account_asset_details_for_investor_master_account` · `sub_account_api.managed_sub_account_snapshot_for_investor_master_account` · `sub_account_api.margin_transfer_for_sub_account_for_master_account` · `sub_account_api.query_managed_sub_account_transfer_log_for_investor_master_account` · `sub_account_api.query_managed_sub_account_transfer_log_for_trading_team_master_account` · `sub_account_api.query_managed_sub_account_transfer_log_for_trading_team_sub_account_user_data` · `sub_account_api.query_managed_sub_account_futures_asset_details_for_investor_master_account` · `sub_account_api.query_managed_sub_account_list_for_investor` · `sub_account_api.query_managed_sub_account_margin_asset_details_for_investor_master_account` · `sub_account_api.query_sub_account_assets_for_master_account` · `sub_account_api.query_sub_account_list_for_master_account` · `sub_account_api.query_sub_account_transaction_statistics_for_master_account` · `sub_account_api.sub_account_assets_for_master_account` · `sub_account_api.sub_account_deposit_history_for_master_account` · `sub_account_api.sub_account_futures_asset_transfer_for_master_account` · `sub_account_api.sub_account_futures_asset_transfer_history_for_master_account` · `sub_account_api.sub_account_spot_asset_transfer_history_for_master_account` · `sub_account_api.sub_account_spot_assets_summary_for_master_account` · `sub_account_api.sub_account_spot_assets_summary_for_master_account_2` · `sub_account_api.sub_account_transfer_history_for_sub_account` · `sub_account_api.sub_account_s_status_on_margin_futures_for_master_account` · `sub_account_api.summary_of_sub_account_s_futures_account_for_master_account` · `sub_account_api.summary_of_sub_account_s_futures_account_v2_for_master_account` · `sub_account_api.summary_of_sub_account_s_margin_account_for_master_account` · `sub_account_api.transfer_for_sub_account_for_master_account` · `sub_account_api.transfer_to_master_for_sub_account` · `sub_account_api.transfer_to_sub_account_of_same_master_for_sub_account` · `sub_account_api.universal_transfer_for_master_account` · `sub_account_api.universal_transfer_history_for_master_account` · `sub_account_api.update_ip_restriction_for_sub_account_api_key_for_master_account` · `sub_account_api.withdrawl_assets_from_the_managed_sub_account_for_investor_master_account` · `trade_api.account_information_user_data` · `trade_api.account_trade_list_user_data` · `trade_api.all_orders_user_data` · `trade_api.cancel_oco_trade` · `trade_api.cancel_order_trade` · `trade_api.cancel_all_open_orders_on_a_symbol_trade` · `trade_api.cancel_an_existing_order_and_send_a_new_order_trade` · `trade_api.current_open_orders_user_data` · `trade_api.new_order_trade` · `trade_api.new_order_list_oto_trade` · `trade_api.new_order_list_otoco_trade` · `trade_api.new_order_list_oco_trade` · `trade_api.new_order_using_sor_trade` · `trade_api.query_allocations_user_data` · `trade_api.query_commission_rates_user_data` · `trade_api.query_current_order_count_usage_trade` · `trade_api.query_oco_user_data` · `trade_api.query_open_oco_user_data` · `trade_api.query_order_user_data` · `trade_api.query_prevented_matches` · `trade_api.query_all_oco_user_data` · `trade_api.test_new_order_trade` · `trade_api.test_new_order_using_sor_trade` · `vip_loans.check_locked_value_of_vip_collateral_account_user_data` · `vip_loans.get_borrow_interest_rate_user_data` · `vip_loans.get_collateral_asset_data_user_data` · `vip_loans.get_loanable_assets_data` · `vip_loans.get_vip_loan_ongoing_orders_user_data` · `vip_loans.get_vip_loan_repayment_history_user_data` · `vip_loans.query_application_status_user_data` · `vip_loans.vip_loan_borrow` · `vip_loans.vip_loan_renew` · `vip_loans.vip_loan_repay_trade` · `wallet.account_api_trading_status_user_data` · `wallet.account_status_user_data` · `wallet.account_info_user_data` · `wallet.all_coins_information_user_data` · `wallet.asset_detail_user_data` · `wallet.asset_dividend_record_user_data` · `wallet.convert_transfer_user_data` · `wallet.daily_account_snapshot_user_data` · `wallet.deposit_address_supporting_network_user_data` · `wallet.deposit_history_supporting_network_user_data` · `wallet.disable_fast_withdraw_switch_user_data` · `wallet.dust_transfer_user_data` · `wallet.dust_log_user_data` · `wallet.enable_fast_withdraw_switch_user_data` · `wallet.fetch_deposit_address_list_with_network_user_data` · `wallet.fetch_withdraw_address_list_user_data` · `wallet.funding_wallet_user_data` · `wallet.get_api_key_permission_user_data` · `wallet.get_assets_that_can_be_converted_into_bnb_user_data` · `wallet.get_cloud_mining_payment_and_refund_history_user_data` · `wallet.get_symbols_delist_schedule_for_spot_market_data` · `wallet.one_click_arrival_deposit_apply_user_data` · `wallet.query_convert_transfer_user_data` · `wallet.query_user_delegation_history_for_master_account_user_data` · `wallet.query_user_universal_transfer_history_user_data` · `wallet.query_user_wallet_balance_user_data` · `wallet.query_auto_converting_stable_coins_user_data` · `wallet.switch_on_off_busd_and_stable_coins_conversion_user_data_user_data` · `wallet.trade_fee_user_data` · `wallet.user_asset_user_data` · `wallet.user_universal_transfer_user_data` · `wallet.withdraw_user_data` · `wallet.withdraw_history_supporting_network_user_data`); distinguishing members `code` · `msg` - 2. *(none)* — `isolated_margin_stream.generate_a_listen_key_user_stream` · `margin_stream.create_a_listen_key_user_stream_2` · `market.check_server_time` · `market.old_trade_lookup` · `market.test_connectivity` · `stream.create_a_listen_key_user_stream` · `wallet.system_status_system`: no typed arm, so `.error` is always `RawError` - 3. So `isinstance(e.error, Error)` matches only 333 of 340 operations. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 306 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"` of the 2 declared. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Binance Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Binance Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Binance Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Binance Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-binance` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `binance/` and `pyproject.toml` of the generated package at version `1.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Binance | +| Distribution name (what you install) | `binance` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `binance` — the same string you install | +| Source repository | https://github.com/context-plugins/binance-python-sdk | +| Source branch | `main` | +| Version | `1.0` | +| Sync client class | `BinanceClient` (alias `Client`) | +| Async client class | `AsyncBinanceClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key_auth`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **API key** in the `X-MBX-APIKEY` header — set `api_key_auth` | +| Environments | 2 environments selected by `environment` (default `"production"`), overridable with `base_url` | +| Base-URL config | `ServerConfig` (`binance/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 340 operations across 29 controllers · 522 models · 15 unions · 62 enums · 333 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "binance @ git+https://github.com/context-plugins/binance-python-sdk.git@main" +python -c "import binance, pathlib; print(pathlib.Path(binance.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from binance import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`binance/__init__.py` exports exactly 6 names beside the `binance.models` subpackage it re-exports: + +```python +from binance import ( + AsyncBinanceClient, + AsyncClient, + BinanceClient, + Client, + Environment, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `binance.models` | +| Enums (and their open `…OrStr` aliases) | `binance.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `binance.core` | +| Per-operation error *unions* | `binance.errors` (`HrTickerPriceChangeStatistics24ErrorBody`, …) | + +`binance.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments + +The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: + +| `environment=` | Base URL | Hosting | +| --- | --- | --- | +| `"production"` *(default)* | `https://api.binance.com` | — | +| `"environment2"` | `https://testnet.binance.vision` | — | + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `environment` gives you **`"production"`**, silently — it is whatever the description listed first, not necessarily production. +- `base_url` overrides the environment's URL entirely when both are passed. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (one scheme) + +An API key sent as the `X-MBX-APIKEY` header, exposed as the client's `api_key_auth=` keyword taking a plain string. + +```python +from binance import Client + +client = Client(api_key_auth="") +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.auto_invest` | `AutoInvest` / `AsyncAutoInvest` | 17 | `change_plan_status` · `get_list_of_plans` · `get_target_asset_roi_data_user_data` · `get_target_asset_list_user_data` · `index_linked_plan_rebalance_details_user_data` · `index_linked_plan_redemption_trade` · … | +| `client.blvt` | `Blvt` / `AsyncBlvt` | 6 | `blvt_info_market_data` · `blvt_user_limit_info_user_data` · `query_subscription_record_user_data` · `redeem_blvt_user_data` · `redemption_record_user_data` · `subscribe_blvt_user_data` | +| `client.c2_c` | `C2C` / `AsyncC2C` | 1 | `get_c2_c_trade_history_user_data` | +| `client.convert` | `Convert` / `AsyncConvert` | 9 | `accept_quote_trade` · `cancel_limit_order_user_data` · `get_convert_trade_history_user_data` · `list_all_convert_pairs` · `order_status_user_data` · `place_limit_order_user_data` · … | +| `client.copy_trading` | `CopyTrading` / `AsyncCopyTrading` | 2 | `get_futures_lead_trader_status_trade` · `get_futures_lead_trading_symbol_whitelist_user_data` | +| `client.crypto_loans` | `CryptoLoans` / `AsyncCryptoLoans` | 21 | `adjust_ltv_flexible_loan_adjust_ltv_trade` · `adjust_ltv_get_flexible_loan_ltv_adjustment_history_user_data` · `borrow_flexible_loan_borrow_trade` · `borrow_get_flexible_loan_borrow_history_user_data` · `borrow_get_flexible_loan_ongoing_orders_user_data` · `check_collateral_repay_rate_user_data` · … | +| `client.dual_investment` | `DualInvestment` / `AsyncDualInvestment` | 5 | `change_auto_compound_status_user_data` · `check_dual_investment_accounts_user_data` · `get_dual_investment_positions_user_data` · `get_dual_investment_product_list_user_data` · `subscribe_dual_investment_products_user_data` | +| `client.fiat` | `Fiat` / `AsyncFiat` | 2 | `fiat_deposit_withdraw_history_user_data` · `fiat_payments_history_user_data` | +| `client.futures` | `Futures` / `AsyncFutures` | 3 | `get_future_account_transaction_history_list_user_data` · `get_future_tick_level_orderbook_historical_data_download_link_user_data` · `new_future_account_transfer_user_data` | +| `client.futures_algo` | `FuturesAlgo` / `AsyncFuturesAlgo` | 6 | `cancel_algo_order_trade` · `query_current_algo_open_orders_user_data` · `query_historical_algo_orders_user_data` · `query_sub_orders_user_data` · `time_weighted_average_price_twap_new_order_trade` · `volume_participation_vp_new_order_trade` | +| `client.gift_card` | `GiftCard` / `AsyncGiftCard` | 6 | `buy_a_binance_code_trade` · `create_a_binance_code_user_data` · `fetch_rsa_public_key_user_data` · `fetch_token_limit_user_data` · `redeem_a_binance_code_user_data` · `verify_a_binance_code_user_data` | +| `client.isolated_margin_stream` | `IsolatedMarginStream` / `AsyncIsolatedMarginStream` | 3 | `close_a_listen_key_user_stream_3` · `generate_a_listen_key_user_stream` · `ping_keep_alive_a_listen_key_user_stream` | +| `client.margin` | `Margin` / `AsyncMargin` | 48 | `adjust_cross_margin_max_leverage_user_data` · `cross_margin_collateral_ratio_market_data` · `disable_isolated_margin_account_trade` · `enable_isolated_margin_account_trade` · `get_all_cross_margin_pairs_market_data` · `get_all_isolated_margin_symbol_user_data` · … | +| `client.margin_stream` | `MarginStream` / `AsyncMarginStream` | 3 | `close_a_listen_key_user_stream_2` · `create_a_listen_key_user_stream_2` · `ping_keep_alive_a_listen_key_user_stream_2` | +| `client.market` | `Market` / `AsyncMarket` | 15 | `hr_ticker_price_change_statistics24` · `check_server_time` · `compressed_aggregate_trades_list` · `current_average_price` · `exchange_information` · `kline_candlestick_data` · … | +| `client.mining` | `Mining` / `AsyncMining` | 13 | `account_list_user_data` · `acquiring_algorithm_market_data` · `acquiring_coin_name_market_data` · `cancel_hashrate_resale_configuration_user_data` · `earnings_list_user_data` · `extra_bonus_list_user_data` · … | +| `client.nft` | `Nft` / `AsyncNft` | 4 | `get_nft_asset_user_data` · `get_nft_deposit_history_user_data` · `get_nft_transaction_history_user_data` · `get_nft_withdraw_history_user_data` | +| `client.pay` | `Pay` / `AsyncPay` | 1 | `get_pay_trade_history_user_data` | +| `client.portfolio_margin` | `PortfolioMargin` / `AsyncPortfolioMargin` | 14 | `bnb_transfer_user_data` · `change_auto_repay_futures_status_user_data` · `fund_auto_collection_user_data` · `fund_collection_by_asset_user_data` · `get_auto_repay_futures_status_user_data` · `get_portfolio_margin_asset_leverage_user_data` · … | +| `client.rebate` | `Rebate` / `AsyncRebate` | 1 | `get_spot_rebate_history_records_user_data` | +| `client.savings` | `Savings` / `AsyncSavings` | 4 | `change_fixed_activity_position_to_daily_position_user_data` · `get_fixed_activity_project_list_user_data` · `get_fixed_activity_project_position_user_data` · `purchase_fixed_activity_project_user_data` | +| `client.simple_earn` | `SimpleEarn` / `AsyncSimpleEarn` | 24 | `get_collateral_record_user_data` · `get_flexible_personal_left_quota_user_data` · `get_flexible_product_position_user_data` · `get_flexible_redemption_record_user_data` · `get_flexible_rewards_history_user_data` · `get_flexible_subscription_preview_user_data` · … | +| `client.spot_algo` | `SpotAlgo` / `AsyncSpotAlgo` | 5 | `cancel_algo_order` · `query_current_algo_open_orders` · `query_historical_algo_orders` · `query_sub_orders` · `time_weighted_average_price_twap_new_order` | +| `client.staking` | `Staking` / `AsyncStaking` | 12 | `eth_staking_account_v2_user_data` · `get_beth_rewards_distribution_history_user_data` · `get_eth_redemption_history_user_data` · `get_eth_staking_history_user_data` · `get_wbeth_rate_history_user_data` · `get_wbeth_rewards_history_user_data` · … | +| `client.stream` | `Stream` / `AsyncStream` | 3 | `close_a_listen_key_user_stream` · `create_a_listen_key_user_stream` · `ping_keep_alive_a_listen_key_user_stream` | +| `client.sub_account_api` | `SubAccountApi` / `AsyncSubAccountApi` | 45 | `create_a_virtual_sub_account_for_master_account` · `delete_ip_list_for_a_sub_account_api_key_for_master_account` · `deposit_assets_into_the_managed_sub_account_for_investor_master_account` · `detail_on_sub_account_s_futures_account_for_master_account` · `detail_on_sub_account_s_futures_account_v2_for_master_account` · `detail_on_sub_account_s_margin_account_for_master_account` · … | +| `client.trade_api` | `TradeApi` / `AsyncTradeApi` | 23 | `account_information_user_data` · `account_trade_list_user_data` · `all_orders_user_data` · `cancel_oco_trade` · `cancel_order_trade` · `cancel_all_open_orders_on_a_symbol_trade` · … | +| `client.vip_loans` | `VipLoans` / `AsyncVipLoans` | 10 | `check_locked_value_of_vip_collateral_account_user_data` · `get_borrow_interest_rate_user_data` · `get_collateral_asset_data_user_data` · `get_loanable_assets_data` · `get_vip_loan_ongoing_orders_user_data` · `get_vip_loan_repayment_history_user_data` · … | +| `client.wallet` | `Wallet` / `AsyncWallet` | 34 | `account_api_trading_status_user_data` · `account_status_user_data` · `account_info_user_data` · `all_coins_information_user_data` · `asset_detail_user_data` · `asset_dividend_record_user_data` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `binance/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `binance/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/binance-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `binance/models/account_profit.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `binance/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import binance, pathlib; print(pathlib.Path(binance.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/binance` on Windows, `.venv/lib/python3.*/site-packages/binance` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `api_key_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — there is **one** typed error body in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `Error` — 333 operations (`auto_invest.change_plan_status` · `auto_invest.get_list_of_plans` · `auto_invest.get_target_asset_roi_data_user_data` · `auto_invest.get_target_asset_list_user_data` · `auto_invest.index_linked_plan_rebalance_details_user_data` · `auto_invest.index_linked_plan_redemption_trade` · `auto_invest.index_linked_plan_redemption_history_user_data` · `auto_invest.investment_plan_adjustment` · `auto_invest.investment_plan_creation_user_data` · `auto_invest.one_time_transaction_trade` · `auto_invest.query_index_details_user_data` · `auto_invest.query_index_linked_plan_position_details_user_data` · `auto_invest.query_one_time_transaction_status_user_data` · `auto_invest.query_all_source_asset_and_target_asset_user_data` · `auto_invest.query_holding_details_of_the_plan` · `auto_invest.query_source_asset_list_user_data` · `auto_invest.query_subscription_transaction_history` · `blvt.blvt_info_market_data` · `blvt.blvt_user_limit_info_user_data` · `blvt.query_subscription_record_user_data` · `blvt.redeem_blvt_user_data` · `blvt.redemption_record_user_data` · `blvt.subscribe_blvt_user_data` · `c2_c.get_c2_c_trade_history_user_data` · `convert.accept_quote_trade` · `convert.cancel_limit_order_user_data` · `convert.get_convert_trade_history_user_data` · `convert.list_all_convert_pairs` · `convert.order_status_user_data` · `convert.place_limit_order_user_data` · `convert.query_limit_open_orders_user_data` · `convert.query_order_quantity_precision_per_asset_user_data` · `convert.send_quote_request_user_data` · `copy_trading.get_futures_lead_trader_status_trade` · `copy_trading.get_futures_lead_trading_symbol_whitelist_user_data` · `crypto_loans.adjust_ltv_flexible_loan_adjust_ltv_trade` · `crypto_loans.adjust_ltv_get_flexible_loan_ltv_adjustment_history_user_data` · `crypto_loans.borrow_flexible_loan_borrow_trade` · `crypto_loans.borrow_get_flexible_loan_borrow_history_user_data` · `crypto_loans.borrow_get_flexible_loan_ongoing_orders_user_data` · `crypto_loans.check_collateral_repay_rate_user_data` · `crypto_loans.crypto_loan_adjust_ltv_trade` · `crypto_loans.crypto_loan_borrow_trade` · `crypto_loans.crypto_loan_customize_margin_call_trade` · `crypto_loans.crypto_loan_repay_trade` · `crypto_loans.get_collateral_assets_data_user_data` · `crypto_loans.get_crypto_loans_borrow_history_user_data` · `crypto_loans.get_crypto_loans_income_history_user_data` · `crypto_loans.get_flexible_loan_assets_data_user_data` · `crypto_loans.get_flexible_loan_collateral_assets_data_user_data` · `crypto_loans.get_loan_ltv_adjustment_history_user_data` · `crypto_loans.get_loan_ongoing_orders_user_data` · `crypto_loans.get_loan_repayment_history_user_data` · `crypto_loans.get_loanable_assets_data_user_data` · `crypto_loans.repay_flexible_loan_repay_trade` · `crypto_loans.repay_get_flexible_loan_repayment_history_user_data` · `dual_investment.change_auto_compound_status_user_data` · `dual_investment.check_dual_investment_accounts_user_data` · `dual_investment.get_dual_investment_positions_user_data` · `dual_investment.get_dual_investment_product_list_user_data` · `dual_investment.subscribe_dual_investment_products_user_data` · `fiat.fiat_deposit_withdraw_history_user_data` · `fiat.fiat_payments_history_user_data` · `futures.get_future_account_transaction_history_list_user_data` · `futures.get_future_tick_level_orderbook_historical_data_download_link_user_data` · `futures.new_future_account_transfer_user_data` · `futures_algo.cancel_algo_order_trade` · `futures_algo.query_current_algo_open_orders_user_data` · `futures_algo.query_historical_algo_orders_user_data` · `futures_algo.query_sub_orders_user_data` · `futures_algo.time_weighted_average_price_twap_new_order_trade` · `futures_algo.volume_participation_vp_new_order_trade` · `gift_card.buy_a_binance_code_trade` · `gift_card.create_a_binance_code_user_data` · `gift_card.fetch_rsa_public_key_user_data` · `gift_card.fetch_token_limit_user_data` · `gift_card.redeem_a_binance_code_user_data` · `gift_card.verify_a_binance_code_user_data` · `isolated_margin_stream.close_a_listen_key_user_stream_3` · `isolated_margin_stream.ping_keep_alive_a_listen_key_user_stream` · `margin.adjust_cross_margin_max_leverage_user_data` · `margin.cross_margin_collateral_ratio_market_data` · `margin.disable_isolated_margin_account_trade` · `margin.enable_isolated_margin_account_trade` · `margin.get_all_cross_margin_pairs_market_data` · `margin.get_all_isolated_margin_symbol_user_data` · `margin.get_all_margin_assets_market_data` · `margin.get_bnb_burn_status_user_data` · `margin.get_cross_margin_transfer_history_user_data` · `margin.get_force_liquidation_record_user_data` · `margin.get_interest_history_user_data` · `margin.get_small_liability_exchange_coin_list_user_data` · `margin.get_small_liability_exchange_history_user_data` · `margin.get_summary_of_margin_account_user_data` · `margin.get_a_future_hourly_interest_rate_user_data` · `margin.get_cross_or_isolated_margin_capital_flow_user_data` · `margin.get_tokens_or_symbols_delist_schedule_for_cross_margin_and_isolated_margin_market_data` · `margin.margin_account_cancel_oco_trade` · `margin.margin_account_cancel_order_trade` · `margin.margin_account_cancel_all_open_orders_on_a_symbol_trade` · `margin.margin_account_new_oco_trade` · `margin.margin_account_new_oto_trade` · `margin.margin_account_new_otoco_trade` · `margin.margin_account_new_order_trade` · `margin.margin_interest_rate_history_user_data` · `margin.margin_account_borrow_repay_margin` · `margin.margin_manual_liquidation_margin` · `margin.query_cross_margin_account_details_user_data` · `margin.query_cross_margin_fee_data_user_data` · `margin.query_current_margin_order_count_usage_trade` · `margin.query_enabled_isolated_margin_account_limit_user_data` · `margin.query_isolated_margin_account_info_user_data` · `margin.query_isolated_margin_fee_data_user_data` · `margin.query_isolated_margin_tier_data_user_data` · `margin.query_liability_coin_leverage_bracket_in_cross_margin_pro_mode_market_data` · `margin.query_margin_account_s_all_orders_user_data` · `margin.query_margin_account_s_oco_user_data` · `margin.query_margin_account_s_open_oco_user_data` · `margin.query_margin_account_s_open_orders_user_data` · `margin.query_margin_account_s_order_user_data` · `margin.query_margin_account_s_trade_list_user_data` · `margin.query_margin_account_s_all_oco_user_data` · `margin.query_margin_available_inventory_user_data` · `margin.query_margin_price_index_market_data` · `margin.query_max_borrow_user_data` · `margin.query_max_transfer_out_amount_user_data` · `margin.query_borrow_repay_records_in_margin_account_user_data` · `margin.toggle_bnb_burn_on_spot_trade_and_margin_interest_user_data` · `margin_stream.close_a_listen_key_user_stream_2` · `margin_stream.ping_keep_alive_a_listen_key_user_stream_2` · `market.hr_ticker_price_change_statistics24` · `market.compressed_aggregate_trades_list` · `market.current_average_price` · `market.exchange_information` · `market.kline_candlestick_data` · `market.order_book` · `market.recent_trades_list` · `market.rolling_window_price_change_statistics` · `market.symbol_order_book_ticker` · `market.symbol_price_ticker` · `market.trading_day_ticker` · `market.ui_klines` · `mining.account_list_user_data` · `mining.acquiring_algorithm_market_data` · `mining.acquiring_coin_name_market_data` · `mining.cancel_hashrate_resale_configuration_user_data` · `mining.earnings_list_user_data` · `mining.extra_bonus_list_user_data` · `mining.hashrate_resale_details_user_data` · `mining.hashrate_resale_list_user_data` · `mining.hashrate_resale_request_user_data` · `mining.mining_account_earning_user_data` · `mining.request_for_detail_miner_list_user_data` · `mining.request_for_miner_list_user_data` · `mining.statistic_list_user_data` · `nft.get_nft_asset_user_data` · `nft.get_nft_deposit_history_user_data` · `nft.get_nft_transaction_history_user_data` · `nft.get_nft_withdraw_history_user_data` · `pay.get_pay_trade_history_user_data` · `portfolio_margin.bnb_transfer_user_data` · `portfolio_margin.change_auto_repay_futures_status_user_data` · `portfolio_margin.fund_auto_collection_user_data` · `portfolio_margin.fund_collection_by_asset_user_data` · `portfolio_margin.get_auto_repay_futures_status_user_data` · `portfolio_margin.get_portfolio_margin_asset_leverage_user_data` · `portfolio_margin.portfolio_margin_account_user_data` · `portfolio_margin.portfolio_margin_bankruptcy_loan_amount_user_data` · `portfolio_margin.portfolio_margin_bankruptcy_loan_repay_user_data` · `portfolio_margin.portfolio_margin_collateral_rate_market_data` · `portfolio_margin.portfolio_margin_pro_tiered_collateral_rate_user_data` · `portfolio_margin.query_classic_portfolio_margin_negative_balance_interest_history_user_data` · `portfolio_margin.query_portfolio_margin_asset_index_price_market_data` · `portfolio_margin.repay_futures_negative_balance_user_data` · `rebate.get_spot_rebate_history_records_user_data` · `savings.change_fixed_activity_position_to_daily_position_user_data` · `savings.get_fixed_activity_project_list_user_data` · `savings.get_fixed_activity_project_position_user_data` · `savings.purchase_fixed_activity_project_user_data` · `simple_earn.get_collateral_record_user_data` · `simple_earn.get_flexible_personal_left_quota_user_data` · `simple_earn.get_flexible_product_position_user_data` · `simple_earn.get_flexible_redemption_record_user_data` · `simple_earn.get_flexible_rewards_history_user_data` · `simple_earn.get_flexible_subscription_preview_user_data` · `simple_earn.get_flexible_subscription_record_user_data` · `simple_earn.get_locked_personal_left_quota_user_data` · `simple_earn.get_locked_product_position_user_data` · `simple_earn.get_locked_redemption_record_user_data` · `simple_earn.get_locked_rewards_history_user_data` · `simple_earn.get_locked_subscription_preview_user_data` · `simple_earn.get_locked_subscription_record_user_data` · `simple_earn.get_rate_history_user_data` · `simple_earn.get_simple_earn_flexible_product_list_user_data` · `simple_earn.get_simple_earn_locked_product_list_user_data` · `simple_earn.redeem_flexible_product_trade` · `simple_earn.redeem_locked_product_trade` · `simple_earn.set_flexible_auto_subscribe_user_data` · `simple_earn.set_locked_auto_subscribe_user_data` · `simple_earn.set_locked_product_redeem_option_user_data` · `simple_earn.simple_account_user_data` · `simple_earn.subscribe_flexible_product_trade` · `simple_earn.subscribe_locked_product_trade` · `spot_algo.cancel_algo_order` · `spot_algo.query_current_algo_open_orders` · `spot_algo.query_historical_algo_orders` · `spot_algo.query_sub_orders` · `spot_algo.time_weighted_average_price_twap_new_order` · `staking.eth_staking_account_v2_user_data` · `staking.get_beth_rewards_distribution_history_user_data` · `staking.get_eth_redemption_history_user_data` · `staking.get_eth_staking_history_user_data` · `staking.get_wbeth_rate_history_user_data` · `staking.get_wbeth_rewards_history_user_data` · `staking.get_wbeth_unwrap_history_user_data` · `staking.get_wbeth_wrap_history_user_data` · `staking.get_current_eth_staking_quota_user_data` · `staking.redeem_eth_trade` · `staking.subscribe_eth_staking_v2_trade` · `staking.wrap_beth_trade` · `stream.close_a_listen_key_user_stream` · `stream.ping_keep_alive_a_listen_key_user_stream` · `sub_account_api.create_a_virtual_sub_account_for_master_account` · `sub_account_api.delete_ip_list_for_a_sub_account_api_key_for_master_account` · `sub_account_api.deposit_assets_into_the_managed_sub_account_for_investor_master_account` · `sub_account_api.detail_on_sub_account_s_futures_account_for_master_account` · `sub_account_api.detail_on_sub_account_s_futures_account_v2_for_master_account` · `sub_account_api.detail_on_sub_account_s_margin_account_for_master_account` · `sub_account_api.enable_futures_for_sub_account_for_master_account` · `sub_account_api.enable_leverage_token_for_sub_account_for_master_account` · `sub_account_api.enable_margin_for_sub_account_for_master_account` · `sub_account_api.enable_options_for_sub_account_for_master_account_user_data` · `sub_account_api.futures_position_risk_of_sub_account_for_master_account` · `sub_account_api.futures_position_risk_of_sub_account_v2_for_master_account` · `sub_account_api.get_ip_restriction_for_a_sub_account_api_key_for_master_account` · `sub_account_api.get_managed_sub_account_deposit_address_for_investor_master_account` · `sub_account_api.managed_sub_account_asset_details_for_investor_master_account` · `sub_account_api.managed_sub_account_snapshot_for_investor_master_account` · `sub_account_api.margin_transfer_for_sub_account_for_master_account` · `sub_account_api.query_managed_sub_account_transfer_log_for_investor_master_account` · `sub_account_api.query_managed_sub_account_transfer_log_for_trading_team_master_account` · `sub_account_api.query_managed_sub_account_transfer_log_for_trading_team_sub_account_user_data` · `sub_account_api.query_managed_sub_account_futures_asset_details_for_investor_master_account` · `sub_account_api.query_managed_sub_account_list_for_investor` · `sub_account_api.query_managed_sub_account_margin_asset_details_for_investor_master_account` · `sub_account_api.query_sub_account_assets_for_master_account` · `sub_account_api.query_sub_account_list_for_master_account` · `sub_account_api.query_sub_account_transaction_statistics_for_master_account` · `sub_account_api.sub_account_assets_for_master_account` · `sub_account_api.sub_account_deposit_history_for_master_account` · `sub_account_api.sub_account_futures_asset_transfer_for_master_account` · `sub_account_api.sub_account_futures_asset_transfer_history_for_master_account` · `sub_account_api.sub_account_spot_asset_transfer_history_for_master_account` · `sub_account_api.sub_account_spot_assets_summary_for_master_account` · `sub_account_api.sub_account_spot_assets_summary_for_master_account_2` · `sub_account_api.sub_account_transfer_history_for_sub_account` · `sub_account_api.sub_account_s_status_on_margin_futures_for_master_account` · `sub_account_api.summary_of_sub_account_s_futures_account_for_master_account` · `sub_account_api.summary_of_sub_account_s_futures_account_v2_for_master_account` · `sub_account_api.summary_of_sub_account_s_margin_account_for_master_account` · `sub_account_api.transfer_for_sub_account_for_master_account` · `sub_account_api.transfer_to_master_for_sub_account` · `sub_account_api.transfer_to_sub_account_of_same_master_for_sub_account` · `sub_account_api.universal_transfer_for_master_account` · `sub_account_api.universal_transfer_history_for_master_account` · `sub_account_api.update_ip_restriction_for_sub_account_api_key_for_master_account` · `sub_account_api.withdrawl_assets_from_the_managed_sub_account_for_investor_master_account` · `trade_api.account_information_user_data` · `trade_api.account_trade_list_user_data` · `trade_api.all_orders_user_data` · `trade_api.cancel_oco_trade` · `trade_api.cancel_order_trade` · `trade_api.cancel_all_open_orders_on_a_symbol_trade` · `trade_api.cancel_an_existing_order_and_send_a_new_order_trade` · `trade_api.current_open_orders_user_data` · `trade_api.new_order_trade` · `trade_api.new_order_list_oto_trade` · `trade_api.new_order_list_otoco_trade` · `trade_api.new_order_list_oco_trade` · `trade_api.new_order_using_sor_trade` · `trade_api.query_allocations_user_data` · `trade_api.query_commission_rates_user_data` · `trade_api.query_current_order_count_usage_trade` · `trade_api.query_oco_user_data` · `trade_api.query_open_oco_user_data` · `trade_api.query_order_user_data` · `trade_api.query_prevented_matches` · `trade_api.query_all_oco_user_data` · `trade_api.test_new_order_trade` · `trade_api.test_new_order_using_sor_trade` · `vip_loans.check_locked_value_of_vip_collateral_account_user_data` · `vip_loans.get_borrow_interest_rate_user_data` · `vip_loans.get_collateral_asset_data_user_data` · `vip_loans.get_loanable_assets_data` · `vip_loans.get_vip_loan_ongoing_orders_user_data` · `vip_loans.get_vip_loan_repayment_history_user_data` · `vip_loans.query_application_status_user_data` · `vip_loans.vip_loan_borrow` · `vip_loans.vip_loan_renew` · `vip_loans.vip_loan_repay_trade` · `wallet.account_api_trading_status_user_data` · `wallet.account_status_user_data` · `wallet.account_info_user_data` · `wallet.all_coins_information_user_data` · `wallet.asset_detail_user_data` · `wallet.asset_dividend_record_user_data` · `wallet.convert_transfer_user_data` · `wallet.daily_account_snapshot_user_data` · `wallet.deposit_address_supporting_network_user_data` · `wallet.deposit_history_supporting_network_user_data` · `wallet.disable_fast_withdraw_switch_user_data` · `wallet.dust_transfer_user_data` · `wallet.dust_log_user_data` · `wallet.enable_fast_withdraw_switch_user_data` · `wallet.fetch_deposit_address_list_with_network_user_data` · `wallet.fetch_withdraw_address_list_user_data` · `wallet.funding_wallet_user_data` · `wallet.get_api_key_permission_user_data` · `wallet.get_assets_that_can_be_converted_into_bnb_user_data` · `wallet.get_cloud_mining_payment_and_refund_history_user_data` · `wallet.get_symbols_delist_schedule_for_spot_market_data` · `wallet.one_click_arrival_deposit_apply_user_data` · `wallet.query_convert_transfer_user_data` · `wallet.query_user_delegation_history_for_master_account_user_data` · `wallet.query_user_universal_transfer_history_user_data` · `wallet.query_user_wallet_balance_user_data` · `wallet.query_auto_converting_stable_coins_user_data` · `wallet.switch_on_off_busd_and_stable_coins_conversion_user_data_user_data` · `wallet.trade_fee_user_data` · `wallet.user_asset_user_data` · `wallet.user_universal_transfer_user_data` · `wallet.withdraw_user_data` · `wallet.withdraw_history_supporting_network_user_data`); distinguishing members `code` · `msg` + 2. *(none)* — `isolated_margin_stream.generate_a_listen_key_user_stream` · `margin_stream.create_a_listen_key_user_stream_2` · `market.check_server_time` · `market.old_trade_lookup` · `market.test_connectivity` · `stream.create_a_listen_key_user_stream` · `wallet.system_status_system`: no typed arm, so `.error` is always `RawError` + 3. So `isinstance(e.error, Error)` matches only 333 of 340 operations. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 306 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"` of the 2 declared. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/binance/skills/python/python-integrate-binance/SKILL.md b/plugins/binance/skills/python/python-integrate-binance/SKILL.md index 5f428691..a16b27f1 100644 --- a/plugins/binance/skills/python/python-integrate-binance/SKILL.md +++ b/plugins/binance/skills/python/python-integrate-binance/SKILL.md @@ -1,97 +1,97 @@ ---- -name: "python-integrate-binance" -description: "MANDATORY FIRST STEP for Binance Python SDK work in a Python project — load this BEFORE you write any code; Python SDK ONLY, never load it for any other language. Applies when asked to integrate Binance in Python — change plan status, query plan lists, roi return list for target asset, weight(ip): 1, to redeem index-linked plan holdings, query source asset to be used for investment, post an investment plan creation, one time transaction — or when a Binance Python SDK call errors or behaves unexpectedly. Knowing the SDK exists is NOT a substitute for loading this, because it carries five binding gates stated NOWHERE else and not inferable from the package — (1) load python-getting-started, confirm the package is actually installed and read the SDK map that ships with it before any lookup, (2) the exact plan-file path and the no-project-file-edits window until a contract sheet with no open lookups exists there and you have read it, (3) the mandatory load of every python-* companion skill the sheet names, (4) sync-vs-async decided once from the host application before the first call, and (5) the memory ban, where every signature, wire alias, error union and enum member comes from a lookup and never from recall or runtime introspection." ---- - -# Binance Python SDK — Integration workflow (lookup layer + contract sheet) - -You do the SDK lookups yourself: `python-getting-started` is your **lookup layer** — load it and ground every contract fact in it (and in the source modules its table names) before writing code. The `python-*` companion skills are a different thing: they are API-agnostic *usage* guidance, they are yours to load, and Step 1c below makes loading them mandatory. - -## Your lookup layer - -- **`python-getting-started`** is the entry point for every SDK need. It carries the SDK's identity (distribution name vs import root, version, Python floor, install-from-source), what the root package exports and the four modules the surface splits across, environments and the base-URL knob, the auth pattern, the controllers, and a **module table** naming the one file that owns each kind of fact. Load it first, always. -- **The SDK map is your lookup surface, and you open it yourself.** The SDK ships `sdk-map.md` and `map/operations/` at its root. There is no helper agent on the Python side — the map is yours to read, not something to delegate. `python-getting-started` tells you how to traverse it: read `sdk-map.md` once, then the one controller page your operations live on. -- **The installed package is the ground truth; the module table is only the locator.** Confirm the package is importable before you rely on a lookup — `python -c "import binance, pathlib; print(pathlib.Path(binance.__file__).parent)"`. **If it is not installed, install it first** — `python-getting-started` carries the command; until then mark the fact `UNVERIFIED`, say what would settle it, and do not fill the hole from memory. (The distribution is not on any package index — it installs from its repository; `python-getting-started` carries the command.) -- **Read scoped.** Those modules carry long design docstrings. `grep -n` for the symbol and read the surrounding lines rather than whole files, and never copy a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. -- **Write a contract sheet with no open lookups** before you implement: exact signatures, the keyword-only boundary, wire aliases, required-vs-`UNSET` members, the `ApiError.error` union per operation, and enum members for the operations in scope. `python-getting-started` ends with a checklist of the rows a Python sheet is incomplete without — treat that list as the checklist for your own sheet, and collect every in-scope operation in ONE pass rather than re-opening a module per member. - -**Scope guard:** the APIMatic-generated Binance **Python SDK** (import root `binance`, distribution `binance`) in **Python projects only**. Unrelated API, or any language other than Python — do nothing; this skill does not apply. - -## Workflow - -**If the user opens with a reported SDK error or unexpected Binance behaviour** (not new feature work), skip the plan-first flow: load `python-getting-started` and `python-error-handling`, look the failing symbol up in the module the table names, and fix from what you find. Otherwise, for implementation work: - -### Step 1 — Plan first (always, for any implementation work) - -Your FIRST action is to load `python-getting-started` and work through the user's full request (all features in scope — one plan covers the whole implementation). Then write the plan and its contract sheet to `/binance-plan.md` — that absolute path, not a location you pick later. - -**Do the read-only prerequisites in the same pass — they need no SDK knowledge and touch no project file:** - -- the repo survey (read-only exploration of conventions, layering, and **whether the project is sync or `async`**) — capture each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; -- **establish the toolchain before you need it**: which environment manager the repo uses (`uv`, `poetry`, `pip` + venv, `pdm`), whether `binance` is already a dependency and at what version, and the exact commands that run the project's tests and type checker. Getting this wrong later costs a broken install mid-implementation — and an uninstalled package costs you the lookup layer itself; -- a baseline run of the project's checks (`pytest`, `mypy`, `ruff` — whatever it has) on the UNTOUCHED tree, so later failures are attributable to your changes; -- credentials/environment verification (per the task's secret-handling rules) — including which host `environment` will select, since omitting it is silent; -- **a read-only smoke of each in-scope operation against the real credential**, run from your scratchpad, not the project, once the map has given you the signatures and before you finalise the sheet. A plan-, region- or entitlement-gated endpoint answers `403` here — the map presents every operation uniformly and cannot know which ones your key may call — and learning that now reorders the plan rather than the build. Skip only operations whose side effects you cannot reverse; -- setting up your task tracking. - -Never use the planning phase to get a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. - -**HARD GATE — no project-file creation or edits until:** `binance-plan.md` EXISTS at the repo root, its contract sheet has **no open lookups**, and you have read it. The gate bars coding "meanwhile"; it does not bar the read-only prerequisite work above. Starting to code before the sheet exists defeats the entire plan-first design. - -**What the gate does not cover:** creating or activating a virtual environment, installing `binance` and the toolchain into it, and the files those writes produce (`.venv/`, a lockfile the installer updates) are **prerequisites, not project-file edits**. On a greenfield repo they are the only way to reach the lookup layer at all, and they are the only writes permitted before the gate. Nothing you author by hand may change. - -**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write (`python-configuration-resilience`, *The same operation arriving twice*). - -| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | -| --- | --- | --- | --- | --- | - -Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. - -⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it; the SDK's own `Idempotency-Key` header is not one — it is new on every call. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in the plan's **Assumptions & Blockers**, not a row. - -⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces. - -**Where that rejection is caught**: the `except` for what the store raises on the second claim — a lookup that returns the earlier result is not an `except`. - -Write `TBD` in **Where in the code** while you plan — every row; it names code not yet written, not a contract fact, so it is not an open lookup. Once the code type-checks, replace each `TBD` individually with two functions, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a function that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. - -### Step 1c — Required reading (do this before you write any code) - -End the contract sheet with a **REQUIRED READING** block whose rows carry inline `MUST load ` pointers. **Load every `python-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. - -**A fixed floor applies regardless of what the sheet says** — the sheet can add to it, never remove from it: `python-error-handling` always (every integration writes an error boundary); `python-client-initialization` before the client is constructed; `python-testing` before the first test file you create or edit, *including* a throwaway verification script that fakes the transport. A sheet row that excuses one of these ("out of scope", "no test suite requested") is not a substitute for loading it. - -These are API-agnostic usage skills; loading them is not a substitute for the lookup layer, and reading a module in the installed package does not remove the need to load the skill for that step. Contract *facts* still come only from your sheet or a fresh lookup. - -Before implementing, check the plan's **Assumptions & Blockers** section: - -- Blocker or major assumption → surface it to the user in plain language, get their answer, and revise `binance-plan.md` in place. -- Minor assumptions only → proceed. - -Full re-planning only on genuine scope change; for a single missing fact mid-implementation, do the lookup, never guess. - -### Step 2 — Implement from the contract sheet - -1. Read `binance-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. When a lookup revises a row, update the file so the sheet and the code never disagree. -2. **Decide sync vs async once, from the host application, before the first call** — and take the decision from the repo survey, not from preference. This SDK ships two complete client classes (`Client` / `AsyncClient`) and there is no bridge between them: a sync client in an `async def` blocks the event loop, and an async client in sync code is a coroutine nobody awaits. Getting this wrong is not a local fix later; it is every call site. The choice also changes the transport-override keyword (`custom_http_client` vs `custom_async_http_client`) and the teardown obligation (`close()` vs `await aclose()`). -3. **Pick the response mode per call, deliberately.** Each operation exists twice: the plain call raises `ApiError` on failure, and its `with_raw_response` peer returns `ApiResult` instead. For the operations that return `None`, the raw peer is the only way to observe the status code — so a call whose outcome you must inspect needs the mode chosen at write time, not retrofitted. -4. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 1c — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire aliases, error unions, enum members) from the contract sheet or a fresh lookup — never re-derive one from a companion. -5. After every change: run the project's **type checker** (`mypy`/`pyright`) as well as its tests. The package ships `py.typed` and is generated under `mypy --strict`, so a type error against it is a real contract violation, not noise — it is the closest thing Python gives you to the compile step. **Treat a clean type check as the gate you do not skip.** If the project has no type checker configured, install `mypy` into the environment and run `mypy --strict` on the files you touched — the SDK is generated under that setting, so it checks cleanly against it. If you genuinely cannot install one, say so in your final report rather than silently substituting a smoke run. Fix non-SDK errors yourself. -6. **Any error involving an SDK type or member** — an `AttributeError`/`TypeError` on a `binance.*` object, a `pydantic.ValidationError` you cannot place, a mypy error naming an SDK type, an unexpected `ApiError` — → go back to `python-getting-started`'s module table and read the one module that owns the fact. Do not attempt more than one self-fix of an SDK-name error before doing that lookup: rewriting from the same knowledge that produced the error is guessing. Remember that a **decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes**, and that `httpx2` transport exceptions arrive unwrapped — so an exception that does not look like an API error may still be one of the SDK's failure kinds. -7. Once the code is complete, open each function the **DUPLICATE CLAIMS** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. -8. Run the project's tests; verify the integration end to end the way the task demands. Retrying is **on by default**, so decide whether the task keeps it, tunes it through `retry_options`, or turns it off with `max_retries` set to `0`, rather than wrapping the call in a retry of your own — and say which you did. - -### Step 3 — Answering pure questions - -A standalone Binance question with no code change: look it up in `python-getting-started` (and the module its table names), then give the grounded answer. Never answer from memory, even for "easy" questions. - -## Anti-patterns — never do these - -- **Always load `python-getting-started` first.** It is your lookup layer, not optional background. (The `python-*` companions are the complement: load the ones the sheet names, per Step 1c.) Don't re-derive a contract *fact* from a companion — exact signatures, wire aliases, error unions and enum members come from the sheet or a fresh lookup. -- **Never write a Binance/SDK fact from memory** — every signature, field name, enum member, and error union in your code must come from the contract sheet or a lookup. And **never write a call from memory "to fix later".** -- **Don't web-search Binance topics to find an implementation detail** — the installed package's own source is the ground truth, and the module table tells you where to look. Public Binance docs describe the REST API, not this SDK's generated surface, and the two disagree on names. -- **Never introspect the SDK at runtime to discover its shape.** `dir()`, `model_fields`, `inspect.signature`, or a REPL poke is the Python-flavoured version of decompiling the package: it answers what exists, never what is *supported*, and it silently invites private attributes into your code. Read the source module instead. -- **Don't grep the package to *locate* a type** — the SDK map is the locator (and says so: *"Never grep for a type"*); grep only *inside* the module its **Type sources** table names, for the symbol. A sweep for a *shape* is a different thing, and is fine: "every member typed `Optional[Any]`", "every discriminator field" are cross-cutting questions nothing indexes, and one targeted `grep -rn` over `models/` is the right tool for them — record what it found on the sheet. -- **Don't vendor, `sys.path`-hack, or editable-install the SDK from a throwaway clone** to make a lookup possible. Install the distribution properly into the project's environment (see `python-getting-started`); an editable install pointed at a clone breaks every import the moment the clone is deleted. -- **Don't create or edit project files before the HARD GATE** in Step 1 — plan and contract sheet first, code second. - +--- +name: "python-integrate-binance" +description: "MANDATORY FIRST STEP for Binance Python SDK work in a Python project — load this BEFORE you write any code; Python SDK ONLY, never load it for any other language. Applies when asked to integrate Binance in Python — change plan status, query plan lists, roi return list for target asset, weight(ip): 1, to redeem index-linked plan holdings, query source asset to be used for investment, post an investment plan creation, one time transaction — or when a Binance Python SDK call errors or behaves unexpectedly. Knowing the SDK exists is NOT a substitute for loading this, because it carries five binding gates stated NOWHERE else and not inferable from the package — (1) load python-getting-started, confirm the package is actually installed and read the SDK map that ships with it before any lookup, (2) the exact plan-file path and the no-project-file-edits window until a contract sheet with no open lookups exists there and you have read it, (3) the mandatory load of every python-* companion skill the sheet names, (4) sync-vs-async decided once from the host application before the first call, and (5) the memory ban, where every signature, wire alias, error union and enum member comes from a lookup and never from recall or runtime introspection." +--- + +# Binance Python SDK — Integration workflow (lookup layer + contract sheet) + +You do the SDK lookups yourself: `python-getting-started` is your **lookup layer** — load it and ground every contract fact in it (and in the source modules its table names) before writing code. The `python-*` companion skills are a different thing: they are API-agnostic *usage* guidance, they are yours to load, and Step 1c below makes loading them mandatory. + +## Your lookup layer + +- **`python-getting-started`** is the entry point for every SDK need. It carries the SDK's identity (distribution name vs import root, version, Python floor, install-from-source), what the root package exports and the four modules the surface splits across, environments and the base-URL knob, the auth pattern, the controllers, and a **module table** naming the one file that owns each kind of fact. Load it first, always. +- **The SDK map is your lookup surface, and you open it yourself.** The SDK ships `sdk-map.md` and `map/operations/` at its root. There is no helper agent on the Python side — the map is yours to read, not something to delegate. `python-getting-started` tells you how to traverse it: read `sdk-map.md` once, then the one controller page your operations live on. +- **The installed package is the ground truth; the module table is only the locator.** Confirm the package is importable before you rely on a lookup — `python -c "import binance, pathlib; print(pathlib.Path(binance.__file__).parent)"`. **If it is not installed, install it first** — `python-getting-started` carries the command; until then mark the fact `UNVERIFIED`, say what would settle it, and do not fill the hole from memory. (The distribution is not on any package index — it installs from its repository; `python-getting-started` carries the command.) +- **Read scoped.** Those modules carry long design docstrings. `grep -n` for the symbol and read the surrounding lines rather than whole files, and never copy a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. +- **Write a contract sheet with no open lookups** before you implement: exact signatures, the keyword-only boundary, wire aliases, required-vs-`UNSET` members, the `ApiError.error` union per operation, and enum members for the operations in scope. `python-getting-started` ends with a checklist of the rows a Python sheet is incomplete without — treat that list as the checklist for your own sheet, and collect every in-scope operation in ONE pass rather than re-opening a module per member. + +**Scope guard:** the APIMatic-generated Binance **Python SDK** (import root `binance`, distribution `binance`) in **Python projects only**. Unrelated API, or any language other than Python — do nothing; this skill does not apply. + +## Workflow + +**If the user opens with a reported SDK error or unexpected Binance behaviour** (not new feature work), skip the plan-first flow: load `python-getting-started` and `python-error-handling`, look the failing symbol up in the module the table names, and fix from what you find. Otherwise, for implementation work: + +### Step 1 — Plan first (always, for any implementation work) + +Your FIRST action is to load `python-getting-started` and work through the user's full request (all features in scope — one plan covers the whole implementation). Then write the plan and its contract sheet to `/binance-plan.md` — that absolute path, not a location you pick later. + +**Do the read-only prerequisites in the same pass — they need no SDK knowledge and touch no project file:** + +- the repo survey (read-only exploration of conventions, layering, and **whether the project is sync or `async`**) — capture each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; +- **establish the toolchain before you need it**: which environment manager the repo uses (`uv`, `poetry`, `pip` + venv, `pdm`), whether `binance` is already a dependency and at what version, and the exact commands that run the project's tests and type checker. Getting this wrong later costs a broken install mid-implementation — and an uninstalled package costs you the lookup layer itself; +- a baseline run of the project's checks (`pytest`, `mypy`, `ruff` — whatever it has) on the UNTOUCHED tree, so later failures are attributable to your changes; +- credentials/environment verification (per the task's secret-handling rules) — including which host `environment` will select, since omitting it is silent; +- **a read-only smoke of each in-scope operation against the real credential**, run from your scratchpad, not the project, once the map has given you the signatures and before you finalise the sheet. A plan-, region- or entitlement-gated endpoint answers `403` here — the map presents every operation uniformly and cannot know which ones your key may call — and learning that now reorders the plan rather than the build. Skip only operations whose side effects you cannot reverse; +- setting up your task tracking. + +Never use the planning phase to get a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. + +**HARD GATE — no project-file creation or edits until:** `binance-plan.md` EXISTS at the repo root, its contract sheet has **no open lookups**, and you have read it. The gate bars coding "meanwhile"; it does not bar the read-only prerequisite work above. Starting to code before the sheet exists defeats the entire plan-first design. + +**What the gate does not cover:** creating or activating a virtual environment, installing `binance` and the toolchain into it, and the files those writes produce (`.venv/`, a lockfile the installer updates) are **prerequisites, not project-file edits**. On a greenfield repo they are the only way to reach the lookup layer at all, and they are the only writes permitted before the gate. Nothing you author by hand may change. + +**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write (`python-configuration-resilience`, *The same operation arriving twice*). + +| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | +| --- | --- | --- | --- | --- | + +Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. + +⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it; the SDK's own `Idempotency-Key` header is not one — it is new on every call. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in the plan's **Assumptions & Blockers**, not a row. + +⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces. + +**Where that rejection is caught**: the `except` for what the store raises on the second claim — a lookup that returns the earlier result is not an `except`. + +Write `TBD` in **Where in the code** while you plan — every row; it names code not yet written, not a contract fact, so it is not an open lookup. Once the code type-checks, replace each `TBD` individually with two functions, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a function that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. + +### Step 1c — Required reading (do this before you write any code) + +End the contract sheet with a **REQUIRED READING** block whose rows carry inline `MUST load ` pointers. **Load every `python-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. + +**A fixed floor applies regardless of what the sheet says** — the sheet can add to it, never remove from it: `python-error-handling` always (every integration writes an error boundary); `python-client-initialization` before the client is constructed; `python-testing` before the first test file you create or edit, *including* a throwaway verification script that fakes the transport. A sheet row that excuses one of these ("out of scope", "no test suite requested") is not a substitute for loading it. + +These are API-agnostic usage skills; loading them is not a substitute for the lookup layer, and reading a module in the installed package does not remove the need to load the skill for that step. Contract *facts* still come only from your sheet or a fresh lookup. + +Before implementing, check the plan's **Assumptions & Blockers** section: + +- Blocker or major assumption → surface it to the user in plain language, get their answer, and revise `binance-plan.md` in place. +- Minor assumptions only → proceed. + +Full re-planning only on genuine scope change; for a single missing fact mid-implementation, do the lookup, never guess. + +### Step 2 — Implement from the contract sheet + +1. Read `binance-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. When a lookup revises a row, update the file so the sheet and the code never disagree. +2. **Decide sync vs async once, from the host application, before the first call** — and take the decision from the repo survey, not from preference. This SDK ships two complete client classes (`Client` / `AsyncClient`) and there is no bridge between them: a sync client in an `async def` blocks the event loop, and an async client in sync code is a coroutine nobody awaits. Getting this wrong is not a local fix later; it is every call site. The choice also changes the transport-override keyword (`custom_http_client` vs `custom_async_http_client`) and the teardown obligation (`close()` vs `await aclose()`). +3. **Pick the response mode per call, deliberately.** Each operation exists twice: the plain call raises `ApiError` on failure, and its `with_raw_response` peer returns `ApiResult` instead. For the operations that return `None`, the raw peer is the only way to observe the status code — so a call whose outcome you must inspect needs the mode chosen at write time, not retrofitted. +4. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 1c — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire aliases, error unions, enum members) from the contract sheet or a fresh lookup — never re-derive one from a companion. +5. After every change: run the project's **type checker** (`mypy`/`pyright`) as well as its tests. The package ships `py.typed` and is generated under `mypy --strict`, so a type error against it is a real contract violation, not noise — it is the closest thing Python gives you to the compile step. **Treat a clean type check as the gate you do not skip.** If the project has no type checker configured, install `mypy` into the environment and run `mypy --strict` on the files you touched — the SDK is generated under that setting, so it checks cleanly against it. If you genuinely cannot install one, say so in your final report rather than silently substituting a smoke run. Fix non-SDK errors yourself. +6. **Any error involving an SDK type or member** — an `AttributeError`/`TypeError` on a `binance.*` object, a `pydantic.ValidationError` you cannot place, a mypy error naming an SDK type, an unexpected `ApiError` — → go back to `python-getting-started`'s module table and read the one module that owns the fact. Do not attempt more than one self-fix of an SDK-name error before doing that lookup: rewriting from the same knowledge that produced the error is guessing. Remember that a **decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes**, and that `httpx2` transport exceptions arrive unwrapped — so an exception that does not look like an API error may still be one of the SDK's failure kinds. +7. Once the code is complete, open each function the **DUPLICATE CLAIMS** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. +8. Run the project's tests; verify the integration end to end the way the task demands. Retrying is **on by default**, so decide whether the task keeps it, tunes it through `retry_options`, or turns it off with `max_retries` set to `0`, rather than wrapping the call in a retry of your own — and say which you did. + +### Step 3 — Answering pure questions + +A standalone Binance question with no code change: look it up in `python-getting-started` (and the module its table names), then give the grounded answer. Never answer from memory, even for "easy" questions. + +## Anti-patterns — never do these + +- **Always load `python-getting-started` first.** It is your lookup layer, not optional background. (The `python-*` companions are the complement: load the ones the sheet names, per Step 1c.) Don't re-derive a contract *fact* from a companion — exact signatures, wire aliases, error unions and enum members come from the sheet or a fresh lookup. +- **Never write a Binance/SDK fact from memory** — every signature, field name, enum member, and error union in your code must come from the contract sheet or a lookup. And **never write a call from memory "to fix later".** +- **Don't web-search Binance topics to find an implementation detail** — the installed package's own source is the ground truth, and the module table tells you where to look. Public Binance docs describe the REST API, not this SDK's generated surface, and the two disagree on names. +- **Never introspect the SDK at runtime to discover its shape.** `dir()`, `model_fields`, `inspect.signature`, or a REPL poke is the Python-flavoured version of decompiling the package: it answers what exists, never what is *supported*, and it silently invites private attributes into your code. Read the source module instead. +- **Don't grep the package to *locate* a type** — the SDK map is the locator (and says so: *"Never grep for a type"*); grep only *inside* the module its **Type sources** table names, for the symbol. A sweep for a *shape* is a different thing, and is fine: "every member typed `Optional[Any]`", "every discriminator field" are cross-cutting questions nothing indexes, and one targeted `grep -rn` over `models/` is the right tool for them — record what it found on the sheet. +- **Don't vendor, `sys.path`-hack, or editable-install the SDK from a throwaway clone** to make a lookup possible. Install the distribution properly into the project's environment (see `python-getting-started`); an editable install pointed at a clone breaks every import the moment the clone is deleted. +- **Don't create or edit project files before the HARD GATE** in Step 1 — plan and contract sheet first, code second. + diff --git a/plugins/coingecko/.claude-plugin/plugin.json b/plugins/coingecko/.claude-plugin/plugin.json index bd07322f..cadcc78f 100644 --- a/plugins/coingecko/.claude-plugin/plugin.json +++ b/plugins/coingecko/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "coingecko", "displayName": "Coin Gecko", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated CoinGecko Demo API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/coingecko/.cursor-plugin/plugin.json b/plugins/coingecko/.cursor-plugin/plugin.json index bd07322f..cadcc78f 100644 --- a/plugins/coingecko/.cursor-plugin/plugin.json +++ b/plugins/coingecko/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "coingecko", "displayName": "Coin Gecko", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated CoinGecko Demo API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/coingecko/README.md b/plugins/coingecko/README.md index acf6cf15..aeddb1e7 100644 --- a/plugins/coingecko/README.md +++ b/plugins/coingecko/README.md @@ -1,13 +1,13 @@ -# CoinGecko Demo API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **CoinGecko Demo API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-coin-gecko`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-coin-gecko`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-coin-gecko`, `typescript-models`, `typescript-testing` | +# CoinGecko Demo API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **CoinGecko Demo API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-coin-gecko`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-coin-gecko`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-coin-gecko`, `typescript-models`, `typescript-testing` | diff --git a/plugins/coingecko/plugin.json b/plugins/coingecko/plugin.json index bd07322f..cadcc78f 100644 --- a/plugins/coingecko/plugin.json +++ b/plugins/coingecko/plugin.json @@ -1,7 +1,7 @@ { "name": "coingecko", "displayName": "Coin Gecko", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated CoinGecko Demo API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/coingecko/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/coingecko/skills/dotnet/dotnet-getting-started/SKILL.md index 5fd3ba4d..994f82a3 100644 --- a/plugins/coingecko/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/coingecko/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Coin Gecko .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-coin-gecko is the workflow that uses it." ---- - -# Getting started with the Coin Gecko .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Coin Gecko SDK fact. `dotnet-integrate-coin-gecko` is the workflow that drives it — you collect the contracts for the work in scope into `coin-gecko-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Coin Gecko | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/coingecko-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `CoinGecko` (the `using` namespace) | -| Client class | `CoinGeckoClient` | -| Options class | `CoinGeckoClientOptions` | -| Auth | **API key** in the `x-cg-demo-api-key` header — set `HeaderAuth`
**API key** in the `x_cg_demo_api_key` query parameter — set `QueryAuth` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://api.coingecko.com/api/v3` | -| Log environment variable | `COINGECKOCLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-coin-gecko` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using CoinGecko.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `CoinGeckoClient` | `CoinGecko` | -| `CoinGeckoClientOptions` | `CoinGecko` | -| `ServerEnvironment` | `CoinGecko.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Coin Gecko, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/CoinGecko/Models/`, `src/CoinGecko/Errors/` and `src/CoinGecko/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/CoinGecko/Models/`, enums `src/CoinGecko/Models/Enums/`, typed errors `src/CoinGecko/Errors/`, request records `src/CoinGecko/Requests/`, or `src/CoinGecko/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `coin-gecko-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/coin-gecko-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/coin-gecko-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/coin-gecko-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/coingecko-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\coin-gecko-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/coingecko-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `coin-gecko-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/CoinGecko/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/CoinGecko/Models/` (+ `src/CoinGecko/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/CoinGecko/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/CoinGecko/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/CoinGecko/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/CoinGecko/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/CoinGecko/Servers/`, `src/CoinGecko/CoinGeckoClient.cs`, `src/CoinGecko/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Coin Gecko may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `COINGECKOCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/CoinGecko/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-coin-gecko`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new CoinGeckoClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `CoinGeckoClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Coin Gecko .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-coin-gecko is the workflow that uses it." +--- + +# Getting started with the Coin Gecko .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Coin Gecko SDK fact. `dotnet-integrate-coin-gecko` is the workflow that drives it — you collect the contracts for the work in scope into `coin-gecko-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Coin Gecko | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/coingecko-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `CoinGecko` (the `using` namespace) | +| Client class | `CoinGeckoClient` | +| Options class | `CoinGeckoClientOptions` | +| Auth | **API key** in the `x-cg-demo-api-key` header — set `HeaderAuth`
**API key** in the `x_cg_demo_api_key` query parameter — set `QueryAuth` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://api.coingecko.com/api/v3` | +| Log environment variable | `COINGECKOCLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-coin-gecko` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using CoinGecko.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `CoinGeckoClient` | `CoinGecko` | +| `CoinGeckoClientOptions` | `CoinGecko` | +| `ServerEnvironment` | `CoinGecko.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Coin Gecko, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/CoinGecko/Models/`, `src/CoinGecko/Errors/` and `src/CoinGecko/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/CoinGecko/Models/`, enums `src/CoinGecko/Models/Enums/`, typed errors `src/CoinGecko/Errors/`, request records `src/CoinGecko/Requests/`, or `src/CoinGecko/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `coin-gecko-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/coin-gecko-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/coin-gecko-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/coin-gecko-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/coingecko-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\coin-gecko-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/coingecko-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `coin-gecko-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/CoinGecko/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/CoinGecko/Models/` (+ `src/CoinGecko/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/CoinGecko/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/CoinGecko/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/CoinGecko/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/CoinGecko/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/CoinGecko/Servers/`, `src/CoinGecko/CoinGeckoClient.cs`, `src/CoinGecko/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Coin Gecko may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `COINGECKOCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/CoinGecko/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-coin-gecko`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new CoinGeckoClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `CoinGeckoClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/coingecko/skills/dotnet/dotnet-integrate-coin-gecko/SKILL.md b/plugins/coingecko/skills/dotnet/dotnet-integrate-coin-gecko/SKILL.md index 1b82cee4..9f565fa8 100644 --- a/plugins/coingecko/skills/dotnet/dotnet-integrate-coin-gecko/SKILL.md +++ b/plugins/coingecko/skills/dotnet/dotnet-integrate-coin-gecko/SKILL.md @@ -1,173 +1,173 @@ ---- -name: "dotnet-integrate-coin-gecko" -description: "MANDATORY FIRST STEP for Coin Gecko .NET SDK work in a C#/.NET project — load this BEFORE opening the SDK map or touching any project file; .NET/C# SDK ONLY, never load it for any other language. Applies when asked to integrate Coin Gecko in C# — Coins, Derivatives, Entities, Exchanges, GlobalApi, Misc, Nfts, Onchain, PublicTreasuryApi, SearchApi, Simple — or when a Coin Gecko .NET SDK call errors or behaves unexpectedly. It carries four binding rules stated NOWHERE else — (1) for implementation work, the plan file coin-gecko-plan.md is written at the project repo root BEFORE any project file is created or edited; (2) dotnet-getting-started and the SDK map it carries are loaded and used for every contract fact, never memory; (3) every dotnet-* skill the contract sheet's REQUIRED READING names is loaded before implementation starts; (4) every sheet row carries one of three labels — a map page, UNVERIFIED, or YOUR CALL — not in the map. Skip it and SDK facts get written from stale memory." ---- - -# Coin Gecko .NET SDK — Workflow (map + skills) - -You are the Coin Gecko .NET SDK specialist for this task — one agent for every SDK need: planning, answering contract questions, implementing, and fixing SDK errors. Your scope is **C#/.NET only**: if a request concerns any other language or SDK, say so and stop. Your source of truth is the **SDK map that ships inside the SDK source repo** (`sdk-map.md` plus its `map/operations/` pages; model, enum and error shapes live in the source files those pages name), obtained per `dotnet-getting-started`, plus the companion `dotnet-*` skills for usage traps. Your training data on this SDK is stale — every fact you emit must come from a map page or a map-named source file you actually read this session, in that same clone. You never guess, and you never open the SDK's `api-reference.md`. You do all of it yourself — ground, plan, implement, fix — there is no helper agent to delegate to. - -**Scope guard:** the APIMatic-generated Coin Gecko **.NET SDK** (root namespace `CoinGecko`) in **C#/.NET projects only**. Unrelated API, or any language other than C#/.NET — do nothing; this skill does not apply. - -## Grounding rules - -- **Map for contracts; the map-named file for shapes.** Follow `dotnet-getting-started`'s *SDK map* and *SDK source* sections in full — they define how to obtain the source (the map lives inside it), that reading a model/enum/error shape means opening the one declaring file the map names (routine, scoped), and why locating anything by grep/glob/`find` over the tree is a defect rather than a shortcut. The clone stays in the system temp directory, and `coin-gecko-plan.md` never carries its path — the plan must stay portable. -- **Never grep, glob or `find` the SDK tree to locate something** — the map is the locator, and a type name is its path. Every lookup starts at the map index and ends on the one page (or the one source file) it names. -- **Your contract sheet never leaves a contract fact open for "later."** The map pages and the files they name answer nearly everything — resolve them during planning, not mid-implementation. For the rare in-scope fact even the source cannot settle: if only live traffic could confirm it, convert it into a concrete defensive-coding directive on the sheet and label the uncertainty `UNVERIFIED`. Never leave a row marked for a later source lookup: an open row is how you end up opening source mid-implementation. -- **When you judge how far a contract can be trusted**, the trust judgment may cite ONLY evidence visible in the map or SDK source (e.g. two generated definitions that disagree, a suspicious shared model) — never training-data memory of this API, and never claims about what the live wire "usually" sends. -- **The sheet records Coin Gecko's call surface; the application's design is decided at implementation time.** Its persistence, its concurrency rules, and the request contract its own callers must satisfy are not SDK facts. Where an SDK fact forces an application decision, record the fact and its consequence on the sheet, mark the decision `YOUR CALL — not in the map`, and decide it at implementation time against the task. -- **Name only what you have read.** Never name a claim, header or route of the application as though you knew it exists — unless you have read that code. Name configuration by its binding key, never by a raw environment variable, and give the default the map documents where there is one: a setting you invent a name for is a setting no deployment will supply. - -## Workflow - -**If the user opens with a reported SDK error or unexpected Coin Gecko behaviour** (not new feature work), go straight to *Step 4 — Fixing SDK errors* below — do not run the plan-first flow for a bug report. Otherwise, for implementation work: - -### Step 1 — Plan first: write `coin-gecko-plan.md` (always, for any implementation work) - -Your FIRST deliverable is `coin-gecko-plan.md` at `/coin-gecko-plan.md` — that exact path, always; never pick another location. It carries the plan and the contract sheet in the format below, and it is the only project-repo file you write before the HARD GATE further down. - -#### How to ground (map-first, one pass) - -1. Load `dotnet-getting-started`; open `sdk-map.md` (the index). -2. From the index, open the **operations pages** for every controller in scope — take signatures (the request record each operation takes, and its required members), return types, error case (A: typed `ApiException<{Operation}Error>` with its `TryGet…` accessors and payload type / B: `ApiException`), and pagination — remembering that a block silent on a defaults-table point means the default applies. Where an operation's *semantics* decide what you must pass, that prose is the method's XML `` in the `Api/` file the page names, and the per-member docs on its request record: read it there. -3. Open, scoped, the **declaring source file** for every request/response model you will reference — the operation's **Type sources** table names it — and take field names WITH their `[JsonPropertyName]` wire names, `required` flags, nullability, and the envelope shape. Enum value lists and union accessors come from their declaring files the same way. -4. Identify which companion skill governs each step in scope. For each, write the trap note as a **named hazard plus a `MUST load` pointer** — *not* as the resolved answer. Naming the hazard ("what `Timeout` actually bounds") is right; resolving it inline ("`Timeout` is per-attempt") is wrong — a resolved trap gives you no reason to load the skill, and the skill carries the parts a one-line note cannot. **Never restate a companion skill's default or semantics in a trap note — not even when you believe it correct.** Write the *consequence*, not the answer. Contract *facts* are the opposite: those you resolve fully and inline. -5. Collect everything in ONE pass — the whole point is that you never have to rediscover a contract mid-coding. - -#### Prerequisites you can run alongside - -These need no SDK knowledge and touch no project file, so do them before or alongside the map work: - -- reaching the SDK source (per `dotnet-getting-started` — the map lives inside it, so this comes before any map work); -- the repo survey (read-only exploration of conventions and layering) — record each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; -- `dotnet restore` and a baseline `dotnet build` / `dotnet test` of the UNTOUCHED solution, so later failures are attributable to your changes; -- locating the SDK reference and its version in the project; -- credentials/environment verification (per the task's secret-handling rules); -- setting up your task tracking. - -Never use this prerequisite work as a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. - -#### HARD GATE - -**No project-file creation or edits until `coin-gecko-plan.md` exists at that path with every section below filled in.** Starting to code before the sheet exists defeats the plan-first design: the sheet is what keeps SDK facts out of your memory and in the map. The gate bars coding before the sheet exists; it does not bar the read-only prerequisite work above. - -#### The `coin-gecko-plan.md` format (keep it tight — tables, not prose) - -1. **Scope & sequence** — the implementation steps in order, each naming the operations it uses. A capability the map lacks is a Blocker (§6), never a data path you invent to replace it. -2. **CONTRACT SHEET** — open the section with two literal warning lines: that signatures are generated code, verbatim — each operation that takes input takes ONE request record as its first parameter (an operation with no inputs takes none), built with an object initializer whose property names are the record's own, never flat arguments; and that every SDK type is written fully-qualified with the namespace its source path implies, taken from the path the map gives for THAT type, never from where a neighbouring type sits. Then one table row per operation: controller property · method signature · request record + its members (`Name: type, required?`) · body model + its fields (`Name (wire_name): type, required?`) · response envelope + the inner fields the integration reads · error case A/B + accessors + payload type · pagination · **source** (§7). Below it: the enum value tables actually needed, and the client construction/auth/server-node facts. ⚠ A request model may mark nothing required, and then `required?` selects nothing for you — carry the optional fields the endpoint's own prose ties to whether the call is accepted, and say which doc-named fields you left out. - -The CONTRACT SHEET also carries a **CROSS-OPERATION INVARIANTS** table, directly below its per-operation rows: the rules no single row can state, because they relate two operations. One row per value the task lets a caller supply where a *different* operation defines the legal set for it (identifiers, handles, codes, references); `none` if the task has no such value — never leave the table out. - -| Invariant | Operations | Enforced where | -| --- | --- | --- | -| a value accepted by X must be one Y returned | `{WriteOperation}` ← `{ListOperation}` | before `{WriteOperation}` is called | - -⚠⚠ The map never states these rules; derive them from the task. - -⚠⚠ **Operations** names the operation that *defines* the legal set: the list or search call that returns what this application offers, or the earlier call whose result this application stored. A lookup that confirms a single value exists is not an answer: it proves the value exists at the provider, not that it is one this application offers. - -**Enforced where** names the member that checks, before the write, that the caller's value is in that set. If the application never reads that set, say so there and add the read — the check is part of the work. `YOUR CALL — not in the map` is not a legal entry in any cell of this table. - -3. **Trap notes** — one line per hazard, attached to the step where it bites, each ending in an inline **`MUST load `** pointer. Name the hazard and its consequence; do not resolve it. ⚠⚠ **"Do not resolve it" is the load-bearing half of this rule.** A trap note that answers its own question hands you a usable one-liner — and holding one, you do not open the skill. You implement from the sentence, and everything the skill carries beyond it never reaches the code. So: state the hazard, state what it costs, hand over the skill, and stop. No fix, no snippet, not even a partial answer. A trap note from which you could write correct code without loading the named skill is a defect. -4. **REQUIRED READING** — close the sheet with the de-duplicated list of every `dotnet-*` skill named above, one line each: skill · the step it governs. Write each name so the copy is unambiguous — every APIMatic .NET plugin ships these same skill names, and a bare name gives you no way to tell which one you loaded. State that these are to be loaded **before implementation starts**, and that the sheet deliberately does not carry their contents. This block is mandatory even when the trap notes are few — an integration always writes an error boundary, so `dotnet-error-handling` always appears here. Always include, verbatim, this hazard row: a body that does not match its declared type — a drifted or malformed **2xx** response (a missing `required` member) or a **non-2xx** body that does not match its operation's generated `{Operation}Error` shape — surfaces as `ResponseDeserializationException`, an `ApiException` that keeps the HTTP status and names the target type but is **not** an `ApiException`; a catch ladder that handles only `ApiException` lets it escape, so it must also catch `ResponseDeserializationException` (or `ApiException`). -5. **PRODUCTION READINESS** — the fixed table below, every row carrying a *decision*. Naming a skill in REQUIRED READING does not address a concern; it defers it. `N/A` is a legitimate answer where it is genuinely true, but it must carry its reason. -6. **Assumptions & Blockers** — anything you had to assume about the user's intent, and anything that blocks planning. An empty section is a valid outcome; an invented fact is not. If you expect the provider to reject a call the plan makes, that is a **Blocker**. -7. Every row's **source** cell cites its map page or the map-named declaring file, so a later lookup is one targeted open, not a search. A row with neither to cite is not a contract fact: write `YOUR CALL — not in the map` there instead. Three labels, one order — a fact the map settles cites that page or file; a fact only live traffic can settle is `UNVERIFIED`; a decision about the application is `YOUR CALL — not in the map`. Something that stops you planning goes in §6. - -| # | Concern | The decision the plan must record | -| --- | --- | --- | -| 1 | **Credential fail-fast** | Where credentials are bound, and that the host refuses to start when one is missing or blank — rather than discovering it as a 401 on the first call in production. A multi-part credential needs **every** part checked, because a blank part is not a missing one. | -| 2 | **Secret sourcing & rotation** | Where the secret comes from, and that the DI registration builds the options object **once at registration** and captures it in the singleton — so a rotated secret does not take effect until the process restarts. If rotation without a restart is required, say how. | -| 3 | **Total timeout budget** | The number the caller actually gets, not the knob. `Timeout` is **per attempt**, so a hung retryable call costs a multiple of it. State the budget and where it is enforced — a `CancellationToken` deadline is the only thing that bounds a whole call. | -| 4 | **Write-retry ownership** | Which of the scope's writes the SDK may resend and which it may not. The default `HttpMethodsToRetry` covers `GET, HEAD, PUT, OPTIONS`, so `POST`/`PATCH`/`DELETE` are never resent by the SDK — and `PUT` **is**. | -| 5 | **Idempotency & ambiguous writes** | For each write in scope: the key it uses, or that none exists. Whether an operation takes a REAL caller-supplied key — and its member name — is on that operation's request record: the record file its map row's **Type sources** table names. The generator-injected `Idempotency-Key` header (`Guid.NewGuid()`, fresh on every call) is **not** a key and must not be cited as one. Where no key exists, record the reconciliation path instead. | -| 6 | **Observability** | What is logged at which level, that **JSON request bodies are logged unredacted** when `LogRequestBody` is on, and which correlation id from the provider's error bodies reaches your own logs. | -| 7 | **Sensitive data** | Whether the scope carries data you would not want in a log — each request model's declaring file lists every field in scope. If it does: `LogRequestBody` stays off **and** `LoggerFactory` is assigned explicitly, so the SDK's log environment variable cannot switch it on from outside the code (see `dotnet-getting-started`). Form bodies are masked only by deny-list, so a key you have not listed prints in the clear. | -| 8 | **Environment selection** | Which base URLs each deployment talks to. Enumerate the server groups and environment members from the map's *Servers & auth* section — there may be more than one group, and each operation resolves through its own. State which groups the scope touches and what each deployment sets; if the SDK declares no sandbox environment, say how test traffic is kept away from the live system. | -| 9 | **Duplicate prevention under concurrency** | For each write a caller can trigger twice: its row in **DUPLICATE CLAIMS** below. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in §6, not a row. | -| 10 | **Partial results** | For each paged read that can stop at a cap: the field or return type that tells the caller the result was cut short. | -| 11 | **Unknown outcomes** | For each write whose connection can fail after the provider may have acted: how the code settles the outcome instead of reporting a failure. | - -A reviewer grades that table alone: each row either records a decision or records why it does not apply. A row that points at a skill, restates the concern, or sits blank is **not addressed**. Rows 5, 7, 8, 9 and 11 are the ones where "not addressed" costs something that cannot be recovered afterwards — a duplicate charge or duplicate write, sensitive data in a log, or test traffic sent to a live system. - -**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write. - -| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | -| --- | --- | --- | --- | --- | - -Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. - -⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it. - -⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces: a primary key is refused even by in-memory test stores; a unique index may not be. - -**Where that rejection is caught**: the `catch` for what the store raises on the second claim — a lookup that returns the earlier result is not a catch. - -Write `TBD` in **Where in the code** while you plan — every row. Once the code compiles, replace each `TBD` individually with two members, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a member that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. - -**PAGED READS** — one row per read that walks pages; `none` if the scope reads no paged list. - -| Read | What caps it | How the caller learns the answer was cut short | Where in the code | -| --- | --- | --- | --- | - -⚠⚠ A log line is not a legal entry in the third column: whoever reads the result never reads your logs. Name the field or return type that changes. Write `TBD` in **Where in the code** while you plan; once the code compiles, replace it with the member that sets that field or return type. A `TBD` left behind is not addressed. - -**UNKNOWN OUTCOMES** — one row per write whose connection can fail after the provider may already have acted; `none` if the scope makes no writes. - -| Write | The operation you re-read with | The reference you search by | Where in the code | The test that fails the connection | -| --- | --- | --- | --- | --- | - -⚠⚠ Reporting a failure is not a legal entry, and neither is leaving the caller to retry. Settle the outcome in the failing write's own `catch`, before it returns, or record it as unknown for code that settles it later — a report someone reads is not that code. With no reference, the third column names what the match uses; if the provider offers no way to find the write, that is a Blocker in §6, not a row. - -**Where in the code**: that `catch`, or the unknown state and the code that settles it — a re-read helper nothing calls is not addressed. **The test that fails the connection** asserts what follows. Both are `TBD` while you plan, replaced once the code compiles. A `TBD` left behind is not addressed. - -Keep the file lean: no copied map pages, no full model dumps, and no clone path — only the operations and fields the scope actually touches. - -### Step 2 — Required reading (do this before you write any code) - -The contract sheet ends with a **REQUIRED READING** block, and its rows carry inline `MUST load ` pointers. **Load every `dotnet-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. If the sheet names none, load `dotnet-error-handling` anyway — every integration writes an error boundary. - -These are API-agnostic usage skills; loading them is not the same as reading the map. Contract *facts* still come only from the sheet or a map lookup. - -Before implementing, check the plan's **Assumptions & Blockers** section: - -- Blocker or major assumption → surface it to the user in plain language, get their answer, then revise `coin-gecko-plan.md` in place with targeted edits — edit the changed rows, append the new section. Rewriting the whole file to change a few rows is a defect. -- Minor assumptions only → proceed. - -Full re-planning only on genuine scope change; for a single missing fact mid-implementation, look it up in the map — never guess. - -### Step 3 — Implement from the contract sheet - -1. Read `coin-gecko-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. A row whose source column says `YOUR CALL — not in the map` is the one exception: that row is a planning-time judgment about YOUR application, so weigh it against the task and follow the task. -2. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 2 — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire names, error accessors, enum values) from the contract sheet or a map lookup — never re-derive one from a companion. -3. After every change: `dotnet build`; fix non-SDK errors yourself. -4. **Any compile or runtime error involving an SDK type or member** (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `CoinGecko.*`, or a provider error at runtime) → *Step 4* below. Do not attempt more than one self-fix of an SDK-name error before switching to that procedure — rewriting from the same knowledge that produced the error is guessing. -5. Once the code is complete, open each member the **DUPLICATE CLAIMS** and **UNKNOWN OUTCOMES** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. -6. Run the project's tests (`dotnet test`); verify the integration end to end the way the task demands. - -### Step 4 — Fixing SDK errors (map-first, in place) - -A compile or runtime error involving an SDK type or member (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `CoinGecko.*`, or a provider error). Resolve it map-first — never patch it by guessing. Load `dotnet-getting-started` first if you have not already — the map is where every fix below comes from. - -1. **Map lookup first.** For an operation symbol, find its block on the controller's operations page; for a model, enum, error or request-record symbol, open the declaring file — its name is its path (`src/CoinGecko/Models/{Type}.cs`, `src/CoinGecko/Models/Enums/{Type}.cs`, `src/CoinGecko/Errors/{Type}.cs`, `src/CoinGecko/Requests/{Type}.cs`, or `src/CoinGecko/Requests/{Controller}/{Type}.cs` for an operation on a controller — an operation with several tags is filed under the first tag it declares), or take it from the operation's Type sources table. If the code contradicts what you read (wrong field name, missed response envelope, wrong param order, wrong namespace), that is the fix. Response envelopes are the classic case: response types often wrap their payload in one field — reads go one level down. -2. **Go one level deeper on a real gap.** If what you read matches the code, or ambiguity remains, open the operation's own method in the `Api/{Controller}.cs` file the page header names — its `` docs and the request it builds — and the per-member docs on its request record, in this session's SDK clone, scoped. Never scan the tree. -3. **Never re-guess.** Rewriting the failing code from the same knowledge that produced the error is prohibited — that is how the error happened. Each failing symbol gets a map/source-grounded answer before its line changes; never mutate payloads, field names, or status handling speculatively to "see if it works". -4. **Fix in place** — edit only the project files involved in the error, grounded in the map row (or the named source file). -5. **Build to verify, and classify the outcome:** compiles clean → run `dotnet test` on the touched tests when they exist; compile error → the fix is not done, resolve it the same map-first way and rebuild; build blocked (output locked) → the solution is running, so stop it if you started it, or ask the user to rather than killing their process. -6. **Runtime / provider errors** — read the provider's error payload through the documented path: the operation's error case and `TryGet…` accessors from its map row; `dotnet-error-handling` for the Case A/B mechanics. Config-shaped failures (401, wrong host, timeouts): check auth, the server-node/base-URL configuration, and retry semantics before touching call sites. - -If a fix corrects a row in `coin-gecko-plan.md`, correct the row in the file too — the sheet stays the record of what was verified. - -### Step 5 — Pure questions - -A standalone Coin Gecko question with no code change: load `dotnet-getting-started`, look it up in the map (open the one source file the map names only on a real gap), and answer with the map page (or source file) it came from. No plan file — just the grounded answers; when several questions arrive batched, answer them all in one pass. Never answer from memory, even for "easy" questions. - -## Anti-patterns — never do these - -- **Never write a Coin Gecko/SDK fact from memory** — every signature, field name, enum value, and error type in your code must come from the contract sheet or a map lookup. And **never write a call from memory "to fix later".** -- **Do not re-derive or double-check a sheet row from memory** — re-open the one map page it cites. -- **Do not re-derive a contract fact from a `dotnet-*` companion** — they are usage guidance; facts come from the map. -- **Do not grep, glob or `find` the SDK tree, and do not open its `api-reference.md`** — the map is the locator; `dotnet-getting-started` says why. -- **Do not web-search Coin Gecko for an implementation detail** — the map and the SDK source it lives in are the ground truth for THIS SDK version, and `CoinGecko` is what the generated source declares, not what public docs describe. -- **Do not resolve a trap note inline on the sheet** — name the hazard and the skill. - +--- +name: "dotnet-integrate-coin-gecko" +description: "MANDATORY FIRST STEP for Coin Gecko .NET SDK work in a C#/.NET project — load this BEFORE opening the SDK map or touching any project file; .NET/C# SDK ONLY, never load it for any other language. Applies when asked to integrate Coin Gecko in C# — Coins, Derivatives, Entities, Exchanges, GlobalApi, Misc, Nfts, Onchain, PublicTreasuryApi, SearchApi, Simple — or when a Coin Gecko .NET SDK call errors or behaves unexpectedly. It carries four binding rules stated NOWHERE else — (1) for implementation work, the plan file coin-gecko-plan.md is written at the project repo root BEFORE any project file is created or edited; (2) dotnet-getting-started and the SDK map it carries are loaded and used for every contract fact, never memory; (3) every dotnet-* skill the contract sheet's REQUIRED READING names is loaded before implementation starts; (4) every sheet row carries one of three labels — a map page, UNVERIFIED, or YOUR CALL — not in the map. Skip it and SDK facts get written from stale memory." +--- + +# Coin Gecko .NET SDK — Workflow (map + skills) + +You are the Coin Gecko .NET SDK specialist for this task — one agent for every SDK need: planning, answering contract questions, implementing, and fixing SDK errors. Your scope is **C#/.NET only**: if a request concerns any other language or SDK, say so and stop. Your source of truth is the **SDK map that ships inside the SDK source repo** (`sdk-map.md` plus its `map/operations/` pages; model, enum and error shapes live in the source files those pages name), obtained per `dotnet-getting-started`, plus the companion `dotnet-*` skills for usage traps. Your training data on this SDK is stale — every fact you emit must come from a map page or a map-named source file you actually read this session, in that same clone. You never guess, and you never open the SDK's `api-reference.md`. You do all of it yourself — ground, plan, implement, fix — there is no helper agent to delegate to. + +**Scope guard:** the APIMatic-generated Coin Gecko **.NET SDK** (root namespace `CoinGecko`) in **C#/.NET projects only**. Unrelated API, or any language other than C#/.NET — do nothing; this skill does not apply. + +## Grounding rules + +- **Map for contracts; the map-named file for shapes.** Follow `dotnet-getting-started`'s *SDK map* and *SDK source* sections in full — they define how to obtain the source (the map lives inside it), that reading a model/enum/error shape means opening the one declaring file the map names (routine, scoped), and why locating anything by grep/glob/`find` over the tree is a defect rather than a shortcut. The clone stays in the system temp directory, and `coin-gecko-plan.md` never carries its path — the plan must stay portable. +- **Never grep, glob or `find` the SDK tree to locate something** — the map is the locator, and a type name is its path. Every lookup starts at the map index and ends on the one page (or the one source file) it names. +- **Your contract sheet never leaves a contract fact open for "later."** The map pages and the files they name answer nearly everything — resolve them during planning, not mid-implementation. For the rare in-scope fact even the source cannot settle: if only live traffic could confirm it, convert it into a concrete defensive-coding directive on the sheet and label the uncertainty `UNVERIFIED`. Never leave a row marked for a later source lookup: an open row is how you end up opening source mid-implementation. +- **When you judge how far a contract can be trusted**, the trust judgment may cite ONLY evidence visible in the map or SDK source (e.g. two generated definitions that disagree, a suspicious shared model) — never training-data memory of this API, and never claims about what the live wire "usually" sends. +- **The sheet records Coin Gecko's call surface; the application's design is decided at implementation time.** Its persistence, its concurrency rules, and the request contract its own callers must satisfy are not SDK facts. Where an SDK fact forces an application decision, record the fact and its consequence on the sheet, mark the decision `YOUR CALL — not in the map`, and decide it at implementation time against the task. +- **Name only what you have read.** Never name a claim, header or route of the application as though you knew it exists — unless you have read that code. Name configuration by its binding key, never by a raw environment variable, and give the default the map documents where there is one: a setting you invent a name for is a setting no deployment will supply. + +## Workflow + +**If the user opens with a reported SDK error or unexpected Coin Gecko behaviour** (not new feature work), go straight to *Step 4 — Fixing SDK errors* below — do not run the plan-first flow for a bug report. Otherwise, for implementation work: + +### Step 1 — Plan first: write `coin-gecko-plan.md` (always, for any implementation work) + +Your FIRST deliverable is `coin-gecko-plan.md` at `/coin-gecko-plan.md` — that exact path, always; never pick another location. It carries the plan and the contract sheet in the format below, and it is the only project-repo file you write before the HARD GATE further down. + +#### How to ground (map-first, one pass) + +1. Load `dotnet-getting-started`; open `sdk-map.md` (the index). +2. From the index, open the **operations pages** for every controller in scope — take signatures (the request record each operation takes, and its required members), return types, error case (A: typed `ApiException<{Operation}Error>` with its `TryGet…` accessors and payload type / B: `ApiException`), and pagination — remembering that a block silent on a defaults-table point means the default applies. Where an operation's *semantics* decide what you must pass, that prose is the method's XML `` in the `Api/` file the page names, and the per-member docs on its request record: read it there. +3. Open, scoped, the **declaring source file** for every request/response model you will reference — the operation's **Type sources** table names it — and take field names WITH their `[JsonPropertyName]` wire names, `required` flags, nullability, and the envelope shape. Enum value lists and union accessors come from their declaring files the same way. +4. Identify which companion skill governs each step in scope. For each, write the trap note as a **named hazard plus a `MUST load` pointer** — *not* as the resolved answer. Naming the hazard ("what `Timeout` actually bounds") is right; resolving it inline ("`Timeout` is per-attempt") is wrong — a resolved trap gives you no reason to load the skill, and the skill carries the parts a one-line note cannot. **Never restate a companion skill's default or semantics in a trap note — not even when you believe it correct.** Write the *consequence*, not the answer. Contract *facts* are the opposite: those you resolve fully and inline. +5. Collect everything in ONE pass — the whole point is that you never have to rediscover a contract mid-coding. + +#### Prerequisites you can run alongside + +These need no SDK knowledge and touch no project file, so do them before or alongside the map work: + +- reaching the SDK source (per `dotnet-getting-started` — the map lives inside it, so this comes before any map work); +- the repo survey (read-only exploration of conventions and layering) — record each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; +- `dotnet restore` and a baseline `dotnet build` / `dotnet test` of the UNTOUCHED solution, so later failures are attributable to your changes; +- locating the SDK reference and its version in the project; +- credentials/environment verification (per the task's secret-handling rules); +- setting up your task tracking. + +Never use this prerequisite work as a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. + +#### HARD GATE + +**No project-file creation or edits until `coin-gecko-plan.md` exists at that path with every section below filled in.** Starting to code before the sheet exists defeats the plan-first design: the sheet is what keeps SDK facts out of your memory and in the map. The gate bars coding before the sheet exists; it does not bar the read-only prerequisite work above. + +#### The `coin-gecko-plan.md` format (keep it tight — tables, not prose) + +1. **Scope & sequence** — the implementation steps in order, each naming the operations it uses. A capability the map lacks is a Blocker (§6), never a data path you invent to replace it. +2. **CONTRACT SHEET** — open the section with two literal warning lines: that signatures are generated code, verbatim — each operation that takes input takes ONE request record as its first parameter (an operation with no inputs takes none), built with an object initializer whose property names are the record's own, never flat arguments; and that every SDK type is written fully-qualified with the namespace its source path implies, taken from the path the map gives for THAT type, never from where a neighbouring type sits. Then one table row per operation: controller property · method signature · request record + its members (`Name: type, required?`) · body model + its fields (`Name (wire_name): type, required?`) · response envelope + the inner fields the integration reads · error case A/B + accessors + payload type · pagination · **source** (§7). Below it: the enum value tables actually needed, and the client construction/auth/server-node facts. ⚠ A request model may mark nothing required, and then `required?` selects nothing for you — carry the optional fields the endpoint's own prose ties to whether the call is accepted, and say which doc-named fields you left out. + +The CONTRACT SHEET also carries a **CROSS-OPERATION INVARIANTS** table, directly below its per-operation rows: the rules no single row can state, because they relate two operations. One row per value the task lets a caller supply where a *different* operation defines the legal set for it (identifiers, handles, codes, references); `none` if the task has no such value — never leave the table out. + +| Invariant | Operations | Enforced where | +| --- | --- | --- | +| a value accepted by X must be one Y returned | `{WriteOperation}` ← `{ListOperation}` | before `{WriteOperation}` is called | + +⚠⚠ The map never states these rules; derive them from the task. + +⚠⚠ **Operations** names the operation that *defines* the legal set: the list or search call that returns what this application offers, or the earlier call whose result this application stored. A lookup that confirms a single value exists is not an answer: it proves the value exists at the provider, not that it is one this application offers. + +**Enforced where** names the member that checks, before the write, that the caller's value is in that set. If the application never reads that set, say so there and add the read — the check is part of the work. `YOUR CALL — not in the map` is not a legal entry in any cell of this table. + +3. **Trap notes** — one line per hazard, attached to the step where it bites, each ending in an inline **`MUST load `** pointer. Name the hazard and its consequence; do not resolve it. ⚠⚠ **"Do not resolve it" is the load-bearing half of this rule.** A trap note that answers its own question hands you a usable one-liner — and holding one, you do not open the skill. You implement from the sentence, and everything the skill carries beyond it never reaches the code. So: state the hazard, state what it costs, hand over the skill, and stop. No fix, no snippet, not even a partial answer. A trap note from which you could write correct code without loading the named skill is a defect. +4. **REQUIRED READING** — close the sheet with the de-duplicated list of every `dotnet-*` skill named above, one line each: skill · the step it governs. Write each name so the copy is unambiguous — every APIMatic .NET plugin ships these same skill names, and a bare name gives you no way to tell which one you loaded. State that these are to be loaded **before implementation starts**, and that the sheet deliberately does not carry their contents. This block is mandatory even when the trap notes are few — an integration always writes an error boundary, so `dotnet-error-handling` always appears here. Always include, verbatim, this hazard row: a body that does not match its declared type — a drifted or malformed **2xx** response (a missing `required` member) or a **non-2xx** body that does not match its operation's generated `{Operation}Error` shape — surfaces as `ResponseDeserializationException`, an `ApiException` that keeps the HTTP status and names the target type but is **not** an `ApiException`; a catch ladder that handles only `ApiException` lets it escape, so it must also catch `ResponseDeserializationException` (or `ApiException`). +5. **PRODUCTION READINESS** — the fixed table below, every row carrying a *decision*. Naming a skill in REQUIRED READING does not address a concern; it defers it. `N/A` is a legitimate answer where it is genuinely true, but it must carry its reason. +6. **Assumptions & Blockers** — anything you had to assume about the user's intent, and anything that blocks planning. An empty section is a valid outcome; an invented fact is not. If you expect the provider to reject a call the plan makes, that is a **Blocker**. +7. Every row's **source** cell cites its map page or the map-named declaring file, so a later lookup is one targeted open, not a search. A row with neither to cite is not a contract fact: write `YOUR CALL — not in the map` there instead. Three labels, one order — a fact the map settles cites that page or file; a fact only live traffic can settle is `UNVERIFIED`; a decision about the application is `YOUR CALL — not in the map`. Something that stops you planning goes in §6. + +| # | Concern | The decision the plan must record | +| --- | --- | --- | +| 1 | **Credential fail-fast** | Where credentials are bound, and that the host refuses to start when one is missing or blank — rather than discovering it as a 401 on the first call in production. A multi-part credential needs **every** part checked, because a blank part is not a missing one. | +| 2 | **Secret sourcing & rotation** | Where the secret comes from, and that the DI registration builds the options object **once at registration** and captures it in the singleton — so a rotated secret does not take effect until the process restarts. If rotation without a restart is required, say how. | +| 3 | **Total timeout budget** | The number the caller actually gets, not the knob. `Timeout` is **per attempt**, so a hung retryable call costs a multiple of it. State the budget and where it is enforced — a `CancellationToken` deadline is the only thing that bounds a whole call. | +| 4 | **Write-retry ownership** | Which of the scope's writes the SDK may resend and which it may not. The default `HttpMethodsToRetry` covers `GET, HEAD, PUT, OPTIONS`, so `POST`/`PATCH`/`DELETE` are never resent by the SDK — and `PUT` **is**. | +| 5 | **Idempotency & ambiguous writes** | For each write in scope: the key it uses, or that none exists. Whether an operation takes a REAL caller-supplied key — and its member name — is on that operation's request record: the record file its map row's **Type sources** table names. The generator-injected `Idempotency-Key` header (`Guid.NewGuid()`, fresh on every call) is **not** a key and must not be cited as one. Where no key exists, record the reconciliation path instead. | +| 6 | **Observability** | What is logged at which level, that **JSON request bodies are logged unredacted** when `LogRequestBody` is on, and which correlation id from the provider's error bodies reaches your own logs. | +| 7 | **Sensitive data** | Whether the scope carries data you would not want in a log — each request model's declaring file lists every field in scope. If it does: `LogRequestBody` stays off **and** `LoggerFactory` is assigned explicitly, so the SDK's log environment variable cannot switch it on from outside the code (see `dotnet-getting-started`). Form bodies are masked only by deny-list, so a key you have not listed prints in the clear. | +| 8 | **Environment selection** | Which base URLs each deployment talks to. Enumerate the server groups and environment members from the map's *Servers & auth* section — there may be more than one group, and each operation resolves through its own. State which groups the scope touches and what each deployment sets; if the SDK declares no sandbox environment, say how test traffic is kept away from the live system. | +| 9 | **Duplicate prevention under concurrency** | For each write a caller can trigger twice: its row in **DUPLICATE CLAIMS** below. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in §6, not a row. | +| 10 | **Partial results** | For each paged read that can stop at a cap: the field or return type that tells the caller the result was cut short. | +| 11 | **Unknown outcomes** | For each write whose connection can fail after the provider may have acted: how the code settles the outcome instead of reporting a failure. | + +A reviewer grades that table alone: each row either records a decision or records why it does not apply. A row that points at a skill, restates the concern, or sits blank is **not addressed**. Rows 5, 7, 8, 9 and 11 are the ones where "not addressed" costs something that cannot be recovered afterwards — a duplicate charge or duplicate write, sensitive data in a log, or test traffic sent to a live system. + +**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write. + +| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | +| --- | --- | --- | --- | --- | + +Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. + +⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it. + +⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces: a primary key is refused even by in-memory test stores; a unique index may not be. + +**Where that rejection is caught**: the `catch` for what the store raises on the second claim — a lookup that returns the earlier result is not a catch. + +Write `TBD` in **Where in the code** while you plan — every row. Once the code compiles, replace each `TBD` individually with two members, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a member that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. + +**PAGED READS** — one row per read that walks pages; `none` if the scope reads no paged list. + +| Read | What caps it | How the caller learns the answer was cut short | Where in the code | +| --- | --- | --- | --- | + +⚠⚠ A log line is not a legal entry in the third column: whoever reads the result never reads your logs. Name the field or return type that changes. Write `TBD` in **Where in the code** while you plan; once the code compiles, replace it with the member that sets that field or return type. A `TBD` left behind is not addressed. + +**UNKNOWN OUTCOMES** — one row per write whose connection can fail after the provider may already have acted; `none` if the scope makes no writes. + +| Write | The operation you re-read with | The reference you search by | Where in the code | The test that fails the connection | +| --- | --- | --- | --- | --- | + +⚠⚠ Reporting a failure is not a legal entry, and neither is leaving the caller to retry. Settle the outcome in the failing write's own `catch`, before it returns, or record it as unknown for code that settles it later — a report someone reads is not that code. With no reference, the third column names what the match uses; if the provider offers no way to find the write, that is a Blocker in §6, not a row. + +**Where in the code**: that `catch`, or the unknown state and the code that settles it — a re-read helper nothing calls is not addressed. **The test that fails the connection** asserts what follows. Both are `TBD` while you plan, replaced once the code compiles. A `TBD` left behind is not addressed. + +Keep the file lean: no copied map pages, no full model dumps, and no clone path — only the operations and fields the scope actually touches. + +### Step 2 — Required reading (do this before you write any code) + +The contract sheet ends with a **REQUIRED READING** block, and its rows carry inline `MUST load ` pointers. **Load every `dotnet-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. If the sheet names none, load `dotnet-error-handling` anyway — every integration writes an error boundary. + +These are API-agnostic usage skills; loading them is not the same as reading the map. Contract *facts* still come only from the sheet or a map lookup. + +Before implementing, check the plan's **Assumptions & Blockers** section: + +- Blocker or major assumption → surface it to the user in plain language, get their answer, then revise `coin-gecko-plan.md` in place with targeted edits — edit the changed rows, append the new section. Rewriting the whole file to change a few rows is a defect. +- Minor assumptions only → proceed. + +Full re-planning only on genuine scope change; for a single missing fact mid-implementation, look it up in the map — never guess. + +### Step 3 — Implement from the contract sheet + +1. Read `coin-gecko-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. A row whose source column says `YOUR CALL — not in the map` is the one exception: that row is a planning-time judgment about YOUR application, so weigh it against the task and follow the task. +2. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 2 — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire names, error accessors, enum values) from the contract sheet or a map lookup — never re-derive one from a companion. +3. After every change: `dotnet build`; fix non-SDK errors yourself. +4. **Any compile or runtime error involving an SDK type or member** (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `CoinGecko.*`, or a provider error at runtime) → *Step 4* below. Do not attempt more than one self-fix of an SDK-name error before switching to that procedure — rewriting from the same knowledge that produced the error is guessing. +5. Once the code is complete, open each member the **DUPLICATE CLAIMS** and **UNKNOWN OUTCOMES** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. +6. Run the project's tests (`dotnet test`); verify the integration end to end the way the task demands. + +### Step 4 — Fixing SDK errors (map-first, in place) + +A compile or runtime error involving an SDK type or member (`CS1061`, `CS0117`, `CS0234`, `CS0104`, `CS1503`, `CS7036`, … on `CoinGecko.*`, or a provider error). Resolve it map-first — never patch it by guessing. Load `dotnet-getting-started` first if you have not already — the map is where every fix below comes from. + +1. **Map lookup first.** For an operation symbol, find its block on the controller's operations page; for a model, enum, error or request-record symbol, open the declaring file — its name is its path (`src/CoinGecko/Models/{Type}.cs`, `src/CoinGecko/Models/Enums/{Type}.cs`, `src/CoinGecko/Errors/{Type}.cs`, `src/CoinGecko/Requests/{Type}.cs`, or `src/CoinGecko/Requests/{Controller}/{Type}.cs` for an operation on a controller — an operation with several tags is filed under the first tag it declares), or take it from the operation's Type sources table. If the code contradicts what you read (wrong field name, missed response envelope, wrong param order, wrong namespace), that is the fix. Response envelopes are the classic case: response types often wrap their payload in one field — reads go one level down. +2. **Go one level deeper on a real gap.** If what you read matches the code, or ambiguity remains, open the operation's own method in the `Api/{Controller}.cs` file the page header names — its `` docs and the request it builds — and the per-member docs on its request record, in this session's SDK clone, scoped. Never scan the tree. +3. **Never re-guess.** Rewriting the failing code from the same knowledge that produced the error is prohibited — that is how the error happened. Each failing symbol gets a map/source-grounded answer before its line changes; never mutate payloads, field names, or status handling speculatively to "see if it works". +4. **Fix in place** — edit only the project files involved in the error, grounded in the map row (or the named source file). +5. **Build to verify, and classify the outcome:** compiles clean → run `dotnet test` on the touched tests when they exist; compile error → the fix is not done, resolve it the same map-first way and rebuild; build blocked (output locked) → the solution is running, so stop it if you started it, or ask the user to rather than killing their process. +6. **Runtime / provider errors** — read the provider's error payload through the documented path: the operation's error case and `TryGet…` accessors from its map row; `dotnet-error-handling` for the Case A/B mechanics. Config-shaped failures (401, wrong host, timeouts): check auth, the server-node/base-URL configuration, and retry semantics before touching call sites. + +If a fix corrects a row in `coin-gecko-plan.md`, correct the row in the file too — the sheet stays the record of what was verified. + +### Step 5 — Pure questions + +A standalone Coin Gecko question with no code change: load `dotnet-getting-started`, look it up in the map (open the one source file the map names only on a real gap), and answer with the map page (or source file) it came from. No plan file — just the grounded answers; when several questions arrive batched, answer them all in one pass. Never answer from memory, even for "easy" questions. + +## Anti-patterns — never do these + +- **Never write a Coin Gecko/SDK fact from memory** — every signature, field name, enum value, and error type in your code must come from the contract sheet or a map lookup. And **never write a call from memory "to fix later".** +- **Do not re-derive or double-check a sheet row from memory** — re-open the one map page it cites. +- **Do not re-derive a contract fact from a `dotnet-*` companion** — they are usage guidance; facts come from the map. +- **Do not grep, glob or `find` the SDK tree, and do not open its `api-reference.md`** — the map is the locator; `dotnet-getting-started` says why. +- **Do not web-search Coin Gecko for an implementation detail** — the map and the SDK source it lives in are the ground truth for THIS SDK version, and `CoinGecko` is what the generated source declares, not what public docs describe. +- **Do not resolve a trap note inline on the sheet** — name the hazard and the skill. + diff --git a/plugins/coingecko/skills/python/python-getting-started/SKILL.md b/plugins/coingecko/skills/python/python-getting-started/SKILL.md index 5c1b8d3e..1b840c08 100644 --- a/plugins/coingecko/skills/python/python-getting-started/SKILL.md +++ b/plugins/coingecko/skills/python/python-getting-started/SKILL.md @@ -1,229 +1,229 @@ ---- -name: "python-getting-started" -description: "Coin Gecko Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Coin Gecko Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Coin Gecko Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Coin Gecko Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-coin-gecko` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `coin_gecko/` and `pyproject.toml` of the generated package at version `3.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Coin Gecko | -| Distribution name (what you install) | `coin-gecko` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `coin_gecko` — not the string you install | -| Source repository | https://github.com/context-plugins/coingecko-python-sdk | -| Source branch | `main` | -| Version | `3.0.0` | -| Sync client class | `CoinGeckoClient` (alias `Client`) | -| Async client class | `AsyncCoinGeckoClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `header_auth` · `query_auth`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **API key** in the `x-cg-demo-api-key` header — set `header_auth` · **API key** in the `x_cg_demo_api_key` query parameter — set `query_auth` | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.coingecko.com/api/v3` | -| Base-URL config | `ServerConfig` (`coin_gecko/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 61 operations across 11 controllers · 192 models · 3 unions · 28 enums · 0 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "coin-gecko @ git+https://github.com/context-plugins/coingecko-python-sdk.git@main" -python -c "import coin_gecko, pathlib; print(pathlib.Path(coin_gecko.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from coin_gecko import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`coin_gecko/__init__.py` exports exactly 5 names beside the `coin_gecko.models` subpackage it re-exports: - -```python -from coin_gecko import ( - AsyncClient, - AsyncCoinGeckoClient, - Client, - CoinGeckoClient, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `coin_gecko.models` | -| Enums (and their open `…OrStr` aliases) | `coin_gecko.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `coin_gecko.core` | -| Per-operation error *unions* | `coin_gecko.errors` — *this SDK declares none* | - -`coin_gecko.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`coin_gecko/server/server_config.py`), and its default is **`https://api.coingecko.com/api/v3`**: - -```python -base_url: str = "https://api.coingecko.com/api/v3" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://api.coingecko.com/api/v3`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (two schemes) - -An API key sent as the `x-cg-demo-api-key` header, exposed as the client's `header_auth=` keyword taking a plain string. - -```python -from coin_gecko import Client - -client = Client(header_auth="") -``` - -An API key sent as the `x_cg_demo_api_key` query parameter, exposed as the client's `query_auth=` keyword taking a plain string. - -```python -from coin_gecko import Client - -client = Client(query_auth="") -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.coins` | `Coins` / `AsyncCoins` | 13 | `coins_categories` · `coins_categories_list` · `coins_contract_address` · `coins_id` · `coins_id_history` · `coins_id_market_chart` · … | -| `client.derivatives` | `Derivatives` / `AsyncDerivatives` | 4 | `derivatives_exchanges` · `derivatives_exchanges_id` · `derivatives_exchanges_list` · `derivatives_tickers` | -| `client.entities` | `Entities` / `AsyncEntities` | 2 | `companies_public_treasury` · `entities_list` | -| `client.exchanges` | `Exchanges` / `AsyncExchanges` | 6 | `exchange_rates` · `exchanges` · `exchanges_id` · `exchanges_id_tickers` · `exchanges_id_volume_chart` · `exchanges_list` | -| `client.global_api` | `GlobalApi` / `AsyncGlobalApi` | 2 | `crypto_global` · `global_defi` | -| `client.misc` | `Misc` / `AsyncMisc` | 3 | `asset_platforms_list` · `ping_server` · `token_lists` | -| `client.nfts` | `Nfts` / `AsyncNfts` | 3 | `nfts_contract_address` · `nfts_id` · `nfts_list` | -| `client.onchain` | `Onchain` / `AsyncOnchain` | 20 | `dexes_list` · `latest_pools_list` · `latest_pools_network` · `networks_list` · `onchain_simple_price` · `pool_address` · … | -| `client.public_treasury_api` | `PublicTreasuryApi` / `AsyncPublicTreasuryApi` | 3 | `public_treasury_entity` · `public_treasury_entity_chart` · `public_treasury_transaction_history` | -| `client.search_api` | `SearchApi` / `AsyncSearchApi` | 2 | `search_data` · `trending_search` | -| `client.simple` | `Simple` / `AsyncSimple` | 3 | `simple_price` · `simple_supported_currencies` · `simple_token_price` | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `coin_gecko/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `coin_gecko/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/coingecko-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `coin_gecko/models/asset_platform.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `coin_gecko/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import coin_gecko, pathlib; print(pathlib.Path(coin_gecko.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/coin_gecko` on Windows, `.venv/lib/python3.*/site-packages/coin_gecko` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `header_auth=` and `query_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 55 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which host the `base_url` selects**, because omitting it is silently the default. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Coin Gecko Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Coin Gecko Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Coin Gecko Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Coin Gecko Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-coin-gecko` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `coin_gecko/` and `pyproject.toml` of the generated package at version `3.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Coin Gecko | +| Distribution name (what you install) | `coin-gecko` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `coin_gecko` — not the string you install | +| Source repository | https://github.com/context-plugins/coingecko-python-sdk | +| Source branch | `main` | +| Version | `3.0.0` | +| Sync client class | `CoinGeckoClient` (alias `Client`) | +| Async client class | `AsyncCoinGeckoClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `header_auth` · `query_auth`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **API key** in the `x-cg-demo-api-key` header — set `header_auth` · **API key** in the `x_cg_demo_api_key` query parameter — set `query_auth` | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.coingecko.com/api/v3` | +| Base-URL config | `ServerConfig` (`coin_gecko/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 61 operations across 11 controllers · 192 models · 3 unions · 28 enums · 0 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "coin-gecko @ git+https://github.com/context-plugins/coingecko-python-sdk.git@main" +python -c "import coin_gecko, pathlib; print(pathlib.Path(coin_gecko.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from coin_gecko import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`coin_gecko/__init__.py` exports exactly 5 names beside the `coin_gecko.models` subpackage it re-exports: + +```python +from coin_gecko import ( + AsyncClient, + AsyncCoinGeckoClient, + Client, + CoinGeckoClient, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `coin_gecko.models` | +| Enums (and their open `…OrStr` aliases) | `coin_gecko.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `coin_gecko.core` | +| Per-operation error *unions* | `coin_gecko.errors` — *this SDK declares none* | + +`coin_gecko.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`coin_gecko/server/server_config.py`), and its default is **`https://api.coingecko.com/api/v3`**: + +```python +base_url: str = "https://api.coingecko.com/api/v3" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://api.coingecko.com/api/v3`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (two schemes) + +An API key sent as the `x-cg-demo-api-key` header, exposed as the client's `header_auth=` keyword taking a plain string. + +```python +from coin_gecko import Client + +client = Client(header_auth="") +``` + +An API key sent as the `x_cg_demo_api_key` query parameter, exposed as the client's `query_auth=` keyword taking a plain string. + +```python +from coin_gecko import Client + +client = Client(query_auth="") +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.coins` | `Coins` / `AsyncCoins` | 13 | `coins_categories` · `coins_categories_list` · `coins_contract_address` · `coins_id` · `coins_id_history` · `coins_id_market_chart` · … | +| `client.derivatives` | `Derivatives` / `AsyncDerivatives` | 4 | `derivatives_exchanges` · `derivatives_exchanges_id` · `derivatives_exchanges_list` · `derivatives_tickers` | +| `client.entities` | `Entities` / `AsyncEntities` | 2 | `companies_public_treasury` · `entities_list` | +| `client.exchanges` | `Exchanges` / `AsyncExchanges` | 6 | `exchange_rates` · `exchanges` · `exchanges_id` · `exchanges_id_tickers` · `exchanges_id_volume_chart` · `exchanges_list` | +| `client.global_api` | `GlobalApi` / `AsyncGlobalApi` | 2 | `crypto_global` · `global_defi` | +| `client.misc` | `Misc` / `AsyncMisc` | 3 | `asset_platforms_list` · `ping_server` · `token_lists` | +| `client.nfts` | `Nfts` / `AsyncNfts` | 3 | `nfts_contract_address` · `nfts_id` · `nfts_list` | +| `client.onchain` | `Onchain` / `AsyncOnchain` | 20 | `dexes_list` · `latest_pools_list` · `latest_pools_network` · `networks_list` · `onchain_simple_price` · `pool_address` · … | +| `client.public_treasury_api` | `PublicTreasuryApi` / `AsyncPublicTreasuryApi` | 3 | `public_treasury_entity` · `public_treasury_entity_chart` · `public_treasury_transaction_history` | +| `client.search_api` | `SearchApi` / `AsyncSearchApi` | 2 | `search_data` · `trending_search` | +| `client.simple` | `Simple` / `AsyncSimple` | 3 | `simple_price` · `simple_supported_currencies` · `simple_token_price` | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `coin_gecko/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `coin_gecko/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/coingecko-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `coin_gecko/models/asset_platform.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `coin_gecko/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import coin_gecko, pathlib; print(pathlib.Path(coin_gecko.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/coin_gecko` on Windows, `.venv/lib/python3.*/site-packages/coin_gecko` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `header_auth=` and `query_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 55 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which host the `base_url` selects**, because omitting it is silently the default. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/coingecko/skills/python/python-integrate-coin-gecko/SKILL.md b/plugins/coingecko/skills/python/python-integrate-coin-gecko/SKILL.md index a5205518..9fb3a62c 100644 --- a/plugins/coingecko/skills/python/python-integrate-coin-gecko/SKILL.md +++ b/plugins/coingecko/skills/python/python-integrate-coin-gecko/SKILL.md @@ -1,97 +1,97 @@ ---- -name: "python-integrate-coin-gecko" -description: "MANDATORY FIRST STEP for Coin Gecko Python SDK work in a Python project — load this BEFORE you write any code; Python SDK ONLY, never load it for any other language. Applies when asked to integrate Coin Gecko in Python — to check the api server status — or when a Coin Gecko Python SDK call errors or behaves unexpectedly. Knowing the SDK exists is NOT a substitute for loading this, because it carries five binding gates stated NOWHERE else and not inferable from the package — (1) load python-getting-started, confirm the package is actually installed and read the SDK map that ships with it before any lookup, (2) the exact plan-file path and the no-project-file-edits window until a contract sheet with no open lookups exists there and you have read it, (3) the mandatory load of every python-* companion skill the sheet names, (4) sync-vs-async decided once from the host application before the first call, and (5) the memory ban, where every signature, wire alias, error union and enum member comes from a lookup and never from recall or runtime introspection." ---- - -# Coin Gecko Python SDK — Integration workflow (lookup layer + contract sheet) - -You do the SDK lookups yourself: `python-getting-started` is your **lookup layer** — load it and ground every contract fact in it (and in the source modules its table names) before writing code. The `python-*` companion skills are a different thing: they are API-agnostic *usage* guidance, they are yours to load, and Step 1c below makes loading them mandatory. - -## Your lookup layer - -- **`python-getting-started`** is the entry point for every SDK need. It carries the SDK's identity (distribution name vs import root, version, Python floor, install-from-source), what the root package exports and the four modules the surface splits across, environments and the base-URL knob, the auth pattern, the controllers, and a **module table** naming the one file that owns each kind of fact. Load it first, always. -- **The SDK map is your lookup surface, and you open it yourself.** The SDK ships `sdk-map.md` and `map/operations/` at its root. There is no helper agent on the Python side — the map is yours to read, not something to delegate. `python-getting-started` tells you how to traverse it: read `sdk-map.md` once, then the one controller page your operations live on. -- **The installed package is the ground truth; the module table is only the locator.** Confirm the package is importable before you rely on a lookup — `python -c "import coin_gecko, pathlib; print(pathlib.Path(coin_gecko.__file__).parent)"`. **If it is not installed, install it first** — `python-getting-started` carries the command; until then mark the fact `UNVERIFIED`, say what would settle it, and do not fill the hole from memory. (The distribution is not on any package index — it installs from its repository; `python-getting-started` carries the command.) -- **Read scoped.** Those modules carry long design docstrings. `grep -n` for the symbol and read the surrounding lines rather than whole files, and never copy a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. -- **Write a contract sheet with no open lookups** before you implement: exact signatures, the keyword-only boundary, wire aliases, required-vs-`UNSET` members, the `ApiError.error` union per operation, and enum members for the operations in scope. `python-getting-started` ends with a checklist of the rows a Python sheet is incomplete without — treat that list as the checklist for your own sheet, and collect every in-scope operation in ONE pass rather than re-opening a module per member. - -**Scope guard:** the APIMatic-generated Coin Gecko **Python SDK** (import root `coin_gecko`, distribution `coin-gecko`) in **Python projects only**. Unrelated API, or any language other than Python — do nothing; this skill does not apply. - -## Workflow - -**If the user opens with a reported SDK error or unexpected Coin Gecko behaviour** (not new feature work), skip the plan-first flow: load `python-getting-started` and `python-error-handling`, look the failing symbol up in the module the table names, and fix from what you find. Otherwise, for implementation work: - -### Step 1 — Plan first (always, for any implementation work) - -Your FIRST action is to load `python-getting-started` and work through the user's full request (all features in scope — one plan covers the whole implementation). Then write the plan and its contract sheet to `/coin-gecko-plan.md` — that absolute path, not a location you pick later. - -**Do the read-only prerequisites in the same pass — they need no SDK knowledge and touch no project file:** - -- the repo survey (read-only exploration of conventions, layering, and **whether the project is sync or `async`**) — capture each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; -- **establish the toolchain before you need it**: which environment manager the repo uses (`uv`, `poetry`, `pip` + venv, `pdm`), whether `coin-gecko` is already a dependency and at what version, and the exact commands that run the project's tests and type checker. Getting this wrong later costs a broken install mid-implementation — and an uninstalled package costs you the lookup layer itself; -- a baseline run of the project's checks (`pytest`, `mypy`, `ruff` — whatever it has) on the UNTOUCHED tree, so later failures are attributable to your changes; -- credentials/environment verification (per the task's secret-handling rules) — including which host `base_url` will select, since omitting it is silent; -- **a read-only smoke of each in-scope operation against the real credential**, run from your scratchpad, not the project, once the map has given you the signatures and before you finalise the sheet. A plan-, region- or entitlement-gated endpoint answers `403` here — the map presents every operation uniformly and cannot know which ones your key may call — and learning that now reorders the plan rather than the build. Skip only operations whose side effects you cannot reverse; -- setting up your task tracking. - -Never use the planning phase to get a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. - -**HARD GATE — no project-file creation or edits until:** `coin-gecko-plan.md` EXISTS at the repo root, its contract sheet has **no open lookups**, and you have read it. The gate bars coding "meanwhile"; it does not bar the read-only prerequisite work above. Starting to code before the sheet exists defeats the entire plan-first design. - -**What the gate does not cover:** creating or activating a virtual environment, installing `coin-gecko` and the toolchain into it, and the files those writes produce (`.venv/`, a lockfile the installer updates) are **prerequisites, not project-file edits**. On a greenfield repo they are the only way to reach the lookup layer at all, and they are the only writes permitted before the gate. Nothing you author by hand may change. - -**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write (`python-configuration-resilience`, *The same operation arriving twice*). - -| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | -| --- | --- | --- | --- | --- | - -Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. - -⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it; the SDK's own `Idempotency-Key` header is not one — it is new on every call. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in the plan's **Assumptions & Blockers**, not a row. - -⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces. - -**Where that rejection is caught**: the `except` for what the store raises on the second claim — a lookup that returns the earlier result is not an `except`. - -Write `TBD` in **Where in the code** while you plan — every row; it names code not yet written, not a contract fact, so it is not an open lookup. Once the code type-checks, replace each `TBD` individually with two functions, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a function that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. - -### Step 1c — Required reading (do this before you write any code) - -End the contract sheet with a **REQUIRED READING** block whose rows carry inline `MUST load ` pointers. **Load every `python-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. - -**A fixed floor applies regardless of what the sheet says** — the sheet can add to it, never remove from it: `python-error-handling` always (every integration writes an error boundary); `python-client-initialization` before the client is constructed; `python-testing` before the first test file you create or edit, *including* a throwaway verification script that fakes the transport. A sheet row that excuses one of these ("out of scope", "no test suite requested") is not a substitute for loading it. - -These are API-agnostic usage skills; loading them is not a substitute for the lookup layer, and reading a module in the installed package does not remove the need to load the skill for that step. Contract *facts* still come only from your sheet or a fresh lookup. - -Before implementing, check the plan's **Assumptions & Blockers** section: - -- Blocker or major assumption → surface it to the user in plain language, get their answer, and revise `coin-gecko-plan.md` in place. -- Minor assumptions only → proceed. - -Full re-planning only on genuine scope change; for a single missing fact mid-implementation, do the lookup, never guess. - -### Step 2 — Implement from the contract sheet - -1. Read `coin-gecko-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. When a lookup revises a row, update the file so the sheet and the code never disagree. -2. **Decide sync vs async once, from the host application, before the first call** — and take the decision from the repo survey, not from preference. This SDK ships two complete client classes (`Client` / `AsyncClient`) and there is no bridge between them: a sync client in an `async def` blocks the event loop, and an async client in sync code is a coroutine nobody awaits. Getting this wrong is not a local fix later; it is every call site. The choice also changes the transport-override keyword (`custom_http_client` vs `custom_async_http_client`) and the teardown obligation (`close()` vs `await aclose()`). -3. **Pick the response mode per call, deliberately.** Each operation exists twice: the plain call raises `ApiError` on failure, and its `with_raw_response` peer returns `ApiResult` instead. For the operations that return `None`, the raw peer is the only way to observe the status code — so a call whose outcome you must inspect needs the mode chosen at write time, not retrofitted. -4. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 1c — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire aliases, error unions, enum members) from the contract sheet or a fresh lookup — never re-derive one from a companion. -5. After every change: run the project's **type checker** (`mypy`/`pyright`) as well as its tests. The package ships `py.typed` and is generated under `mypy --strict`, so a type error against it is a real contract violation, not noise — it is the closest thing Python gives you to the compile step. **Treat a clean type check as the gate you do not skip.** If the project has no type checker configured, install `mypy` into the environment and run `mypy --strict` on the files you touched — the SDK is generated under that setting, so it checks cleanly against it. If you genuinely cannot install one, say so in your final report rather than silently substituting a smoke run. Fix non-SDK errors yourself. -6. **Any error involving an SDK type or member** — an `AttributeError`/`TypeError` on a `coin_gecko.*` object, a `pydantic.ValidationError` you cannot place, a mypy error naming an SDK type, an unexpected `ApiError` — → go back to `python-getting-started`'s module table and read the one module that owns the fact. Do not attempt more than one self-fix of an SDK-name error before doing that lookup: rewriting from the same knowledge that produced the error is guessing. Remember that a **decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes**, and that `httpx2` transport exceptions arrive unwrapped — so an exception that does not look like an API error may still be one of the SDK's failure kinds. -7. Once the code is complete, open each function the **DUPLICATE CLAIMS** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. -8. Run the project's tests; verify the integration end to end the way the task demands. Retrying is **on by default**, so decide whether the task keeps it, tunes it through `retry_options`, or turns it off with `max_retries` set to `0`, rather than wrapping the call in a retry of your own — and say which you did. - -### Step 3 — Answering pure questions - -A standalone Coin Gecko question with no code change: look it up in `python-getting-started` (and the module its table names), then give the grounded answer. Never answer from memory, even for "easy" questions. - -## Anti-patterns — never do these - -- **Always load `python-getting-started` first.** It is your lookup layer, not optional background. (The `python-*` companions are the complement: load the ones the sheet names, per Step 1c.) Don't re-derive a contract *fact* from a companion — exact signatures, wire aliases, error unions and enum members come from the sheet or a fresh lookup. -- **Never write a Coin Gecko/SDK fact from memory** — every signature, field name, enum member, and error union in your code must come from the contract sheet or a lookup. And **never write a call from memory "to fix later".** -- **Don't web-search Coin Gecko topics to find an implementation detail** — the installed package's own source is the ground truth, and the module table tells you where to look. Public Coin Gecko docs describe the REST API, not this SDK's generated surface, and the two disagree on names. -- **Never introspect the SDK at runtime to discover its shape.** `dir()`, `model_fields`, `inspect.signature`, or a REPL poke is the Python-flavoured version of decompiling the package: it answers what exists, never what is *supported*, and it silently invites private attributes into your code. Read the source module instead. -- **Don't grep the package to *locate* a type** — the SDK map is the locator (and says so: *"Never grep for a type"*); grep only *inside* the module its **Type sources** table names, for the symbol. A sweep for a *shape* is a different thing, and is fine: "every member typed `Optional[Any]`", "every discriminator field" are cross-cutting questions nothing indexes, and one targeted `grep -rn` over `models/` is the right tool for them — record what it found on the sheet. -- **Don't vendor, `sys.path`-hack, or editable-install the SDK from a throwaway clone** to make a lookup possible. Install the distribution properly into the project's environment (see `python-getting-started`); an editable install pointed at a clone breaks every import the moment the clone is deleted. -- **Don't create or edit project files before the HARD GATE** in Step 1 — plan and contract sheet first, code second. - +--- +name: "python-integrate-coin-gecko" +description: "MANDATORY FIRST STEP for Coin Gecko Python SDK work in a Python project — load this BEFORE you write any code; Python SDK ONLY, never load it for any other language. Applies when asked to integrate Coin Gecko in Python — to check the api server status — or when a Coin Gecko Python SDK call errors or behaves unexpectedly. Knowing the SDK exists is NOT a substitute for loading this, because it carries five binding gates stated NOWHERE else and not inferable from the package — (1) load python-getting-started, confirm the package is actually installed and read the SDK map that ships with it before any lookup, (2) the exact plan-file path and the no-project-file-edits window until a contract sheet with no open lookups exists there and you have read it, (3) the mandatory load of every python-* companion skill the sheet names, (4) sync-vs-async decided once from the host application before the first call, and (5) the memory ban, where every signature, wire alias, error union and enum member comes from a lookup and never from recall or runtime introspection." +--- + +# Coin Gecko Python SDK — Integration workflow (lookup layer + contract sheet) + +You do the SDK lookups yourself: `python-getting-started` is your **lookup layer** — load it and ground every contract fact in it (and in the source modules its table names) before writing code. The `python-*` companion skills are a different thing: they are API-agnostic *usage* guidance, they are yours to load, and Step 1c below makes loading them mandatory. + +## Your lookup layer + +- **`python-getting-started`** is the entry point for every SDK need. It carries the SDK's identity (distribution name vs import root, version, Python floor, install-from-source), what the root package exports and the four modules the surface splits across, environments and the base-URL knob, the auth pattern, the controllers, and a **module table** naming the one file that owns each kind of fact. Load it first, always. +- **The SDK map is your lookup surface, and you open it yourself.** The SDK ships `sdk-map.md` and `map/operations/` at its root. There is no helper agent on the Python side — the map is yours to read, not something to delegate. `python-getting-started` tells you how to traverse it: read `sdk-map.md` once, then the one controller page your operations live on. +- **The installed package is the ground truth; the module table is only the locator.** Confirm the package is importable before you rely on a lookup — `python -c "import coin_gecko, pathlib; print(pathlib.Path(coin_gecko.__file__).parent)"`. **If it is not installed, install it first** — `python-getting-started` carries the command; until then mark the fact `UNVERIFIED`, say what would settle it, and do not fill the hole from memory. (The distribution is not on any package index — it installs from its repository; `python-getting-started` carries the command.) +- **Read scoped.** Those modules carry long design docstrings. `grep -n` for the symbol and read the surrounding lines rather than whole files, and never copy a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. +- **Write a contract sheet with no open lookups** before you implement: exact signatures, the keyword-only boundary, wire aliases, required-vs-`UNSET` members, the `ApiError.error` union per operation, and enum members for the operations in scope. `python-getting-started` ends with a checklist of the rows a Python sheet is incomplete without — treat that list as the checklist for your own sheet, and collect every in-scope operation in ONE pass rather than re-opening a module per member. + +**Scope guard:** the APIMatic-generated Coin Gecko **Python SDK** (import root `coin_gecko`, distribution `coin-gecko`) in **Python projects only**. Unrelated API, or any language other than Python — do nothing; this skill does not apply. + +## Workflow + +**If the user opens with a reported SDK error or unexpected Coin Gecko behaviour** (not new feature work), skip the plan-first flow: load `python-getting-started` and `python-error-handling`, look the failing symbol up in the module the table names, and fix from what you find. Otherwise, for implementation work: + +### Step 1 — Plan first (always, for any implementation work) + +Your FIRST action is to load `python-getting-started` and work through the user's full request (all features in scope — one plan covers the whole implementation). Then write the plan and its contract sheet to `/coin-gecko-plan.md` — that absolute path, not a location you pick later. + +**Do the read-only prerequisites in the same pass — they need no SDK knowledge and touch no project file:** + +- the repo survey (read-only exploration of conventions, layering, and **whether the project is sync or `async`**) — capture each convention as *pattern + the ONE exemplar file path to imitate*, NOT inline code snippets: you will read the exemplar at edit time anyway, so a snippet dump gets paid for twice; +- **establish the toolchain before you need it**: which environment manager the repo uses (`uv`, `poetry`, `pip` + venv, `pdm`), whether `coin-gecko` is already a dependency and at what version, and the exact commands that run the project's tests and type checker. Getting this wrong later costs a broken install mid-implementation — and an uninstalled package costs you the lookup layer itself; +- a baseline run of the project's checks (`pytest`, `mypy`, `ruff` — whatever it has) on the UNTOUCHED tree, so later failures are attributable to your changes; +- credentials/environment verification (per the task's secret-handling rules) — including which host `base_url` will select, since omitting it is silent; +- **a read-only smoke of each in-scope operation against the real credential**, run from your scratchpad, not the project, once the map has given you the signatures and before you finalise the sheet. A plan-, region- or entitlement-gated endpoint answers `403` here — the map presents every operation uniformly and cannot know which ones your key may call — and learning that now reorders the plan rather than the build. Skip only operations whose side effects you cannot reverse; +- setting up your task tracking. + +Never use the planning phase to get a "head start" on implementation: **creating or editing ANY project file before the gate below is a defect**, no matter how obvious the code seems. + +**HARD GATE — no project-file creation or edits until:** `coin-gecko-plan.md` EXISTS at the repo root, its contract sheet has **no open lookups**, and you have read it. The gate bars coding "meanwhile"; it does not bar the read-only prerequisite work above. Starting to code before the sheet exists defeats the entire plan-first design. + +**What the gate does not cover:** creating or activating a virtual environment, installing `coin-gecko` and the toolchain into it, and the files those writes produce (`.venv/`, a lockfile the installer updates) are **prerequisites, not project-file edits**. On a greenfield repo they are the only way to reach the lookup layer at all, and they are the only writes permitted before the gate. Nothing you author by hand may change. + +**DUPLICATE CLAIMS** — one row per write a caller can trigger twice; `none` if the task has no such write (`python-configuration-resilience`, *The same operation arriving twice*). + +| Write | Where the claim is stored | What rejects the second one | Where that rejection is caught | Where in the code | +| --- | --- | --- | --- | --- | + +Every row follows one order: **claim → SDK call → record the result**. The claim is a write of its own, made before the SDK call, so a second caller is stopped before it reaches the provider; release it when the provider refuses the call. + +⚠⚠ **Where the claim is stored**: a store this application owns — the one the codebase already keeps its records in — never the provider. A provider idempotency key goes beside the claim, never in place of it; the SDK's own `Idempotency-Key` header is not one — it is new on every call. If this codebase has nowhere to keep a claim that outlives the process, that is a Blocker in the plan's **Assumptions & Blockers**, not a row. + +⚠⚠ **What rejects the second one**: the store refusing the second caller's claim, the way this codebase already refuses a duplicate. A conflict on the save *after* the SDK call is too late — both callers have already reached the provider. A read before the write — a status check, a lookup by key — is not a refusal either: it lets both callers through. Pick a refusal the store you run against enforces. + +**Where that rejection is caught**: the `except` for what the store raises on the second claim — a lookup that returns the earlier result is not an `except`. + +Write `TBD` in **Where in the code** while you plan — every row; it names code not yet written, not a contract fact, so it is not an open lookup. Once the code type-checks, replace each `TBD` individually with two functions, in order: the claim write, then the SDK call it precedes. Only this column changes after coding starts. ⚠⚠ A `TBD` left behind, a function that is not in the code, or one note covering several rows is **not addressed**; if the code does not do what the row says, fix the code, then the cell. + +### Step 1c — Required reading (do this before you write any code) + +End the contract sheet with a **REQUIRED READING** block whose rows carry inline `MUST load ` pointers. **Load every `python-*` skill the sheet names, now, before you start implementing** — not lazily at the step that needs it. The sheet deliberately does *not* carry the how-to: it names the hazard and hands you the skill that resolves it, so an unloaded pointer is a gap in what you know, not a formality. + +**A fixed floor applies regardless of what the sheet says** — the sheet can add to it, never remove from it: `python-error-handling` always (every integration writes an error boundary); `python-client-initialization` before the client is constructed; `python-testing` before the first test file you create or edit, *including* a throwaway verification script that fakes the transport. A sheet row that excuses one of these ("out of scope", "no test suite requested") is not a substitute for loading it. + +These are API-agnostic usage skills; loading them is not a substitute for the lookup layer, and reading a module in the installed package does not remove the need to load the skill for that step. Contract *facts* still come only from your sheet or a fresh lookup. + +Before implementing, check the plan's **Assumptions & Blockers** section: + +- Blocker or major assumption → surface it to the user in plain language, get their answer, and revise `coin-gecko-plan.md` in place. +- Minor assumptions only → proceed. + +Full re-planning only on genuine scope change; for a single missing fact mid-implementation, do the lookup, never guess. + +### Step 2 — Implement from the contract sheet + +1. Read `coin-gecko-plan.md` once. Treat its contracts as authoritative — do not re-derive or "double-check" them from memory. When a lookup revises a row, update the file so the sheet and the code never disagree. +2. **Decide sync vs async once, from the host application, before the first call** — and take the decision from the repo survey, not from preference. This SDK ships two complete client classes (`Client` / `AsyncClient`) and there is no bridge between them: a sync client in an `async def` blocks the event loop, and an async client in sync code is a coroutine nobody awaits. Getting this wrong is not a local fix later; it is every call site. The choice also changes the transport-override keyword (`custom_http_client` vs `custom_async_http_client`) and the teardown obligation (`close()` vs `await aclose()`). +3. **Pick the response mode per call, deliberately.** Each operation exists twice: the plain call raises `ApiError` on failure, and its `with_raw_response` peer returns `ApiResult` instead. For the operations that return `None`, the raw peer is the only way to observe the status code — so a call whose outcome you must inspect needs the mode chosen at write time, not retrofitted. +4. Implement sequentially, following the repo's own conventions and layering. You loaded the companion skills the sheet named in Step 1c — implement each step in line with the one that governs it. Take every contract *fact* (signatures, wire aliases, error unions, enum members) from the contract sheet or a fresh lookup — never re-derive one from a companion. +5. After every change: run the project's **type checker** (`mypy`/`pyright`) as well as its tests. The package ships `py.typed` and is generated under `mypy --strict`, so a type error against it is a real contract violation, not noise — it is the closest thing Python gives you to the compile step. **Treat a clean type check as the gate you do not skip.** If the project has no type checker configured, install `mypy` into the environment and run `mypy --strict` on the files you touched — the SDK is generated under that setting, so it checks cleanly against it. If you genuinely cannot install one, say so in your final report rather than silently substituting a smoke run. Fix non-SDK errors yourself. +6. **Any error involving an SDK type or member** — an `AttributeError`/`TypeError` on a `coin_gecko.*` object, a `pydantic.ValidationError` you cannot place, a mypy error naming an SDK type, an unexpected `ApiError` — → go back to `python-getting-started`'s module table and read the one module that owns the fact. Do not attempt more than one self-fix of an SDK-name error before doing that lookup: rewriting from the same knowledge that produced the error is guessing. Remember that a **decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes**, and that `httpx2` transport exceptions arrive unwrapped — so an exception that does not look like an API error may still be one of the SDK's failure kinds. +7. Once the code is complete, open each function the **DUPLICATE CLAIMS** rows name and confirm it runs when that write runs. Where the code falls short of a row, fix the code, not the row. +8. Run the project's tests; verify the integration end to end the way the task demands. Retrying is **on by default**, so decide whether the task keeps it, tunes it through `retry_options`, or turns it off with `max_retries` set to `0`, rather than wrapping the call in a retry of your own — and say which you did. + +### Step 3 — Answering pure questions + +A standalone Coin Gecko question with no code change: look it up in `python-getting-started` (and the module its table names), then give the grounded answer. Never answer from memory, even for "easy" questions. + +## Anti-patterns — never do these + +- **Always load `python-getting-started` first.** It is your lookup layer, not optional background. (The `python-*` companions are the complement: load the ones the sheet names, per Step 1c.) Don't re-derive a contract *fact* from a companion — exact signatures, wire aliases, error unions and enum members come from the sheet or a fresh lookup. +- **Never write a Coin Gecko/SDK fact from memory** — every signature, field name, enum member, and error union in your code must come from the contract sheet or a lookup. And **never write a call from memory "to fix later".** +- **Don't web-search Coin Gecko topics to find an implementation detail** — the installed package's own source is the ground truth, and the module table tells you where to look. Public Coin Gecko docs describe the REST API, not this SDK's generated surface, and the two disagree on names. +- **Never introspect the SDK at runtime to discover its shape.** `dir()`, `model_fields`, `inspect.signature`, or a REPL poke is the Python-flavoured version of decompiling the package: it answers what exists, never what is *supported*, and it silently invites private attributes into your code. Read the source module instead. +- **Don't grep the package to *locate* a type** — the SDK map is the locator (and says so: *"Never grep for a type"*); grep only *inside* the module its **Type sources** table names, for the symbol. A sweep for a *shape* is a different thing, and is fine: "every member typed `Optional[Any]`", "every discriminator field" are cross-cutting questions nothing indexes, and one targeted `grep -rn` over `models/` is the right tool for them — record what it found on the sheet. +- **Don't vendor, `sys.path`-hack, or editable-install the SDK from a throwaway clone** to make a lookup possible. Install the distribution properly into the project's environment (see `python-getting-started`); an editable install pointed at a clone breaks every import the moment the clone is deleted. +- **Don't create or edit project files before the HARD GATE** in Step 1 — plan and contract sheet first, code second. + diff --git a/plugins/finnhub/.claude-plugin/plugin.json b/plugins/finnhub/.claude-plugin/plugin.json index 5a19a3bb..8312793d 100644 --- a/plugins/finnhub/.claude-plugin/plugin.json +++ b/plugins/finnhub/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "finnhub", "displayName": "Finnhub API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Finnhub API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/finnhub/.cursor-plugin/plugin.json b/plugins/finnhub/.cursor-plugin/plugin.json index 5a19a3bb..8312793d 100644 --- a/plugins/finnhub/.cursor-plugin/plugin.json +++ b/plugins/finnhub/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "finnhub", "displayName": "Finnhub API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Finnhub API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/finnhub/README.md b/plugins/finnhub/README.md index c6b18ed5..0413c323 100644 --- a/plugins/finnhub/README.md +++ b/plugins/finnhub/README.md @@ -1,13 +1,13 @@ -# Finnhub API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Finnhub API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-finnhub`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-finnhub`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-finnhub`, `typescript-models`, `typescript-testing` | +# Finnhub API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Finnhub API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-finnhub`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-finnhub`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-finnhub`, `typescript-models`, `typescript-testing` | diff --git a/plugins/finnhub/plugin.json b/plugins/finnhub/plugin.json index 5a19a3bb..8312793d 100644 --- a/plugins/finnhub/plugin.json +++ b/plugins/finnhub/plugin.json @@ -1,7 +1,7 @@ { "name": "finnhub", "displayName": "Finnhub API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Finnhub API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/finnhub/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/finnhub/skills/dotnet/dotnet-getting-started/SKILL.md index d30839b4..80342a58 100644 --- a/plugins/finnhub/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/finnhub/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Finnhub API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-finnhub-api is the workflow that uses it." ---- - -# Getting started with the Finnhub API .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Finnhub API SDK fact. `dotnet-integrate-finnhub-api` is the workflow that drives it — you collect the contracts for the work in scope into `finnhub-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Finnhub API | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/finnhub-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `FinnhubApi` (the `using` namespace) | -| Client class | `FinnhubApiClient` | -| Options class | `FinnhubApiClientOptions` | -| Auth | **API key** in the `token` query parameter — set `ApiKey` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://finnhub.io/api/v1` | -| Log environment variable | `FINNHUBAPICLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-finnhub-api` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using FinnhubApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `FinnhubApiClient` | `FinnhubApi` | -| `FinnhubApiClientOptions` | `FinnhubApi` | -| `ServerEnvironment` | `FinnhubApi.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Finnhub API, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/FinnhubApi/Models/`, `src/FinnhubApi/Errors/` and `src/FinnhubApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/FinnhubApi/Models/`, enums `src/FinnhubApi/Models/Enums/`, typed errors `src/FinnhubApi/Errors/`, request records `src/FinnhubApi/Requests/`, or `src/FinnhubApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `finnhub-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/finnhub-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/finnhub-api-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/finnhub-api-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/finnhub-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\finnhub-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/finnhub-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `finnhub-api-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/FinnhubApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/FinnhubApi/Models/` (+ `src/FinnhubApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/FinnhubApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/FinnhubApi/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/FinnhubApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/FinnhubApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/FinnhubApi/Servers/`, `src/FinnhubApi/FinnhubApiClient.cs`, `src/FinnhubApi/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Finnhub API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `FINNHUBAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/FinnhubApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-finnhub-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new FinnhubApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `FinnhubApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Finnhub API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-finnhub-api is the workflow that uses it." +--- + +# Getting started with the Finnhub API .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Finnhub API SDK fact. `dotnet-integrate-finnhub-api` is the workflow that drives it — you collect the contracts for the work in scope into `finnhub-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Finnhub API | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/finnhub-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `FinnhubApi` (the `using` namespace) | +| Client class | `FinnhubApiClient` | +| Options class | `FinnhubApiClientOptions` | +| Auth | **API key** in the `token` query parameter — set `ApiKey` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://finnhub.io/api/v1` | +| Log environment variable | `FINNHUBAPICLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-finnhub-api` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using FinnhubApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `FinnhubApiClient` | `FinnhubApi` | +| `FinnhubApiClientOptions` | `FinnhubApi` | +| `ServerEnvironment` | `FinnhubApi.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Finnhub API, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/FinnhubApi/Models/`, `src/FinnhubApi/Errors/` and `src/FinnhubApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/FinnhubApi/Models/`, enums `src/FinnhubApi/Models/Enums/`, typed errors `src/FinnhubApi/Errors/`, request records `src/FinnhubApi/Requests/`, or `src/FinnhubApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `finnhub-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/finnhub-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/finnhub-api-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/finnhub-api-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/finnhub-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\finnhub-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/finnhub-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `finnhub-api-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/FinnhubApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/FinnhubApi/Models/` (+ `src/FinnhubApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/FinnhubApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/FinnhubApi/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/FinnhubApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/FinnhubApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/FinnhubApi/Servers/`, `src/FinnhubApi/FinnhubApiClient.cs`, `src/FinnhubApi/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Finnhub API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `FINNHUBAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/FinnhubApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-finnhub-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new FinnhubApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `FinnhubApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/finnhub/skills/python/python-getting-started/SKILL.md b/plugins/finnhub/skills/python/python-getting-started/SKILL.md index 96a9c3a4..b6038e08 100644 --- a/plugins/finnhub/skills/python/python-getting-started/SKILL.md +++ b/plugins/finnhub/skills/python/python-getting-started/SKILL.md @@ -1,225 +1,225 @@ ---- -name: "python-getting-started" -description: "Finnhub API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Finnhub API Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Finnhub API Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Finnhub API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-finnhub-api` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `finnhub_api/` and `pyproject.toml` of the generated package at version `1.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Finnhub API | -| Distribution name (what you install) | `finnhub-api` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `finnhub_api` — not the string you install | -| Source repository | https://github.com/context-plugins/finnhub-python-sdk | -| Source branch | `main` | -| Version | `1.0.0` | -| Sync client class | `FinnhubApiClient` (alias `Client`) | -| Async client class | `AsyncFinnhubApiClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **API key** in the `token` query parameter — set `api_key` | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://finnhub.io/api/v1` | -| Base-URL config | `ServerConfig` (`finnhub_api/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 117 operations across 18 controllers · 192 models · 0 unions · 0 enums · 0 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "finnhub-api @ git+https://github.com/context-plugins/finnhub-python-sdk.git@main" -python -c "import finnhub_api, pathlib; print(pathlib.Path(finnhub_api.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across three modules - -Python does not re-export child modules transitively, so `from finnhub_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`finnhub_api/__init__.py` exports exactly 5 names beside the `finnhub_api.models` subpackage it re-exports: - -```python -from finnhub_api import ( - AsyncClient, - AsyncFinnhubApiClient, - Client, - FinnhubApiClient, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `finnhub_api.models` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `finnhub_api.core` | -| Per-operation error *unions* | `finnhub_api.errors` — *this SDK declares none* | - -`finnhub_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`finnhub_api/server/server_config.py`), and its default is **`https://finnhub.io/api/v1`**: - -```python -base_url: str = "https://finnhub.io/api/v1" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://finnhub.io/api/v1`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (one scheme) - -An API key sent as the `token` query parameter, exposed as the client's `api_key=` keyword taking a plain string. - -```python -from finnhub_api import Client - -client = Client(api_key="") -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.bond` | `Bond` / `AsyncBond` | 4 | `bond_price` · `bond_profile` · `bond_tick` · `bond_yield_curve` | -| `client.calendar` | `Calendar` / `AsyncCalendar` | 3 | `earnings_calendar` · `economic_calendar` · `ipo_calendar` | -| `client.corporate_actions` | `CorporateActions` / `AsyncCorporateActions` | 2 | `isin_change` · `symbol_change` | -| `client.crypto` | `Crypto` / `AsyncCrypto` | 4 | `crypto_candles` · `crypto_exchanges` · `crypto_profile` · `crypto_symbols` | -| `client.economic` | `Economic` / `AsyncEconomic` | 2 | `economic_code` · `economic_data` | -| `client.etf` | `Etf` / `AsyncEtf` | 5 | `etfs_allocation` · `etfs_country_exposure` · `etfs_holdings` · `etfs_profile` · `etfs_sector_exposure` | -| `client.forex` | `Forex` / `AsyncForex` | 4 | `forex_candles` · `forex_exchanges` · `forex_rates` · `forex_symbols` | -| `client.global_filings` | `GlobalFilings` / `AsyncGlobalFilings` | 4 | `global_filings_download` · `global_filings_search` · `global_filings_search_filter` · `search_in_filing` | -| `client.index` | `Index` / `AsyncIndex` | 2 | `indices_constituents` · `indices_historical_constituents` | -| `client.institutional` | `Institutional` / `AsyncInstitutional` | 3 | `institutional_ownership` · `institutional_portfolio` · `institutional_profile` | -| `client.misc` | `Misc` / `AsyncMisc` | 10 | `ai_chat` · `airline_price_index` · `bank_branch` · `country` · `covid_19` · `fda_committee_meeting_calendar` · … | -| `client.mutual_fund` | `MutualFund` / `AsyncMutualFund` | 6 | `mutual_fund_country_exposure` · `mutual_fund_eet` · `mutual_fund_eet_pai` · `mutual_fund_holdings` · `mutual_fund_profile` · `mutual_fund_sector_exposure` | -| `client.news` | `News` / `AsyncNews` | 4 | `company_news` · `market_news` · `news_sentiment` · `press_releases` | -| `client.scan` | `Scan` / `AsyncScan` | 3 | `aggregate_indicator` · `pattern_recognition` · `support_resistance` | -| `client.stock_estimates` | `StockEstimates` / `AsyncStockEstimates` | 18 | `company_capex_estimates` · `company_dps_estimates` · `company_earnings` · `company_earnings_quality_score` · `company_ebit_estimates` · `company_ebitda_estimates` · … | -| `client.stock_fundamentals` | `StockFundamentals` / `AsyncStockFundamentals` | 18 | `company_basic_financials` · `company_esg_score` · `company_executive` · `company_historical_esg_score` · `company_profile` · `company_profile2` · … | -| `client.stock_ownership` | `StockOwnership` / `AsyncStockOwnership` | 12 | `congressional_trading` · `fund_ownership` · `insider_sentiment` · `insider_transactions` · `investment_themes` · `ownership` · … | -| `client.stock_prices` | `StockPrices` / `AsyncStockPrices` | 13 | `company_peers` · `historical_market_cap` · `market_holiday` · `market_status` · `price_metrics` · `stock_basic_dividends` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `finnhub_api/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `finnhub_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/finnhub-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `finnhub_api/models/aichat_body.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `finnhub_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import finnhub_api, pathlib; print(pathlib.Path(finnhub_api.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/finnhub_api` on Windows, `.venv/lib/python3.*/site-packages/finnhub_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `api_key=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **1 operation returns `None`**, so `with_raw_response` is the only way to observe its status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 1 operation that returns `None`** — `global_filings.global_filings_download`. Its raw peer is `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares no required member on any return type, so a truncated body decodes without complaint and the hole surfaces later.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which host the `base_url` selects**, because omitting it is silently the default. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Finnhub API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Finnhub API Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Finnhub API Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Finnhub API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-finnhub-api` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `finnhub_api/` and `pyproject.toml` of the generated package at version `1.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Finnhub API | +| Distribution name (what you install) | `finnhub-api` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `finnhub_api` — not the string you install | +| Source repository | https://github.com/context-plugins/finnhub-python-sdk | +| Source branch | `main` | +| Version | `1.0.0` | +| Sync client class | `FinnhubApiClient` (alias `Client`) | +| Async client class | `AsyncFinnhubApiClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **API key** in the `token` query parameter — set `api_key` | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://finnhub.io/api/v1` | +| Base-URL config | `ServerConfig` (`finnhub_api/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 117 operations across 18 controllers · 192 models · 0 unions · 0 enums · 0 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "finnhub-api @ git+https://github.com/context-plugins/finnhub-python-sdk.git@main" +python -c "import finnhub_api, pathlib; print(pathlib.Path(finnhub_api.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across three modules + +Python does not re-export child modules transitively, so `from finnhub_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`finnhub_api/__init__.py` exports exactly 5 names beside the `finnhub_api.models` subpackage it re-exports: + +```python +from finnhub_api import ( + AsyncClient, + AsyncFinnhubApiClient, + Client, + FinnhubApiClient, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `finnhub_api.models` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `finnhub_api.core` | +| Per-operation error *unions* | `finnhub_api.errors` — *this SDK declares none* | + +`finnhub_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`finnhub_api/server/server_config.py`), and its default is **`https://finnhub.io/api/v1`**: + +```python +base_url: str = "https://finnhub.io/api/v1" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://finnhub.io/api/v1`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (one scheme) + +An API key sent as the `token` query parameter, exposed as the client's `api_key=` keyword taking a plain string. + +```python +from finnhub_api import Client + +client = Client(api_key="") +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.bond` | `Bond` / `AsyncBond` | 4 | `bond_price` · `bond_profile` · `bond_tick` · `bond_yield_curve` | +| `client.calendar` | `Calendar` / `AsyncCalendar` | 3 | `earnings_calendar` · `economic_calendar` · `ipo_calendar` | +| `client.corporate_actions` | `CorporateActions` / `AsyncCorporateActions` | 2 | `isin_change` · `symbol_change` | +| `client.crypto` | `Crypto` / `AsyncCrypto` | 4 | `crypto_candles` · `crypto_exchanges` · `crypto_profile` · `crypto_symbols` | +| `client.economic` | `Economic` / `AsyncEconomic` | 2 | `economic_code` · `economic_data` | +| `client.etf` | `Etf` / `AsyncEtf` | 5 | `etfs_allocation` · `etfs_country_exposure` · `etfs_holdings` · `etfs_profile` · `etfs_sector_exposure` | +| `client.forex` | `Forex` / `AsyncForex` | 4 | `forex_candles` · `forex_exchanges` · `forex_rates` · `forex_symbols` | +| `client.global_filings` | `GlobalFilings` / `AsyncGlobalFilings` | 4 | `global_filings_download` · `global_filings_search` · `global_filings_search_filter` · `search_in_filing` | +| `client.index` | `Index` / `AsyncIndex` | 2 | `indices_constituents` · `indices_historical_constituents` | +| `client.institutional` | `Institutional` / `AsyncInstitutional` | 3 | `institutional_ownership` · `institutional_portfolio` · `institutional_profile` | +| `client.misc` | `Misc` / `AsyncMisc` | 10 | `ai_chat` · `airline_price_index` · `bank_branch` · `country` · `covid_19` · `fda_committee_meeting_calendar` · … | +| `client.mutual_fund` | `MutualFund` / `AsyncMutualFund` | 6 | `mutual_fund_country_exposure` · `mutual_fund_eet` · `mutual_fund_eet_pai` · `mutual_fund_holdings` · `mutual_fund_profile` · `mutual_fund_sector_exposure` | +| `client.news` | `News` / `AsyncNews` | 4 | `company_news` · `market_news` · `news_sentiment` · `press_releases` | +| `client.scan` | `Scan` / `AsyncScan` | 3 | `aggregate_indicator` · `pattern_recognition` · `support_resistance` | +| `client.stock_estimates` | `StockEstimates` / `AsyncStockEstimates` | 18 | `company_capex_estimates` · `company_dps_estimates` · `company_earnings` · `company_earnings_quality_score` · `company_ebit_estimates` · `company_ebitda_estimates` · … | +| `client.stock_fundamentals` | `StockFundamentals` / `AsyncStockFundamentals` | 18 | `company_basic_financials` · `company_esg_score` · `company_executive` · `company_historical_esg_score` · `company_profile` · `company_profile2` · … | +| `client.stock_ownership` | `StockOwnership` / `AsyncStockOwnership` | 12 | `congressional_trading` · `fund_ownership` · `insider_sentiment` · `insider_transactions` · `investment_themes` · `ownership` · … | +| `client.stock_prices` | `StockPrices` / `AsyncStockPrices` | 13 | `company_peers` · `historical_market_cap` · `market_holiday` · `market_status` · `price_metrics` · `stock_basic_dividends` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `finnhub_api/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `finnhub_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/finnhub-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `finnhub_api/models/aichat_body.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `finnhub_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import finnhub_api, pathlib; print(pathlib.Path(finnhub_api.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/finnhub_api` on Windows, `.venv/lib/python3.*/site-packages/finnhub_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `api_key=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **1 operation returns `None`**, so `with_raw_response` is the only way to observe its status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 1 operation that returns `None`** — `global_filings.global_filings_download`. Its raw peer is `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares no required member on any return type, so a truncated body decodes without complaint and the hole surfaces later.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which host the `base_url` selects**, because omitting it is silently the default. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/firecrawl/.claude-plugin/plugin.json b/plugins/firecrawl/.claude-plugin/plugin.json index e45a2521..26a5b3b0 100644 --- a/plugins/firecrawl/.claude-plugin/plugin.json +++ b/plugins/firecrawl/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "firecrawl", "displayName": "Firecrawl API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Firecrawl API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/firecrawl/.cursor-plugin/plugin.json b/plugins/firecrawl/.cursor-plugin/plugin.json index e45a2521..26a5b3b0 100644 --- a/plugins/firecrawl/.cursor-plugin/plugin.json +++ b/plugins/firecrawl/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "firecrawl", "displayName": "Firecrawl API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Firecrawl API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/firecrawl/README.md b/plugins/firecrawl/README.md index 08fa611e..2a8d2dbb 100644 --- a/plugins/firecrawl/README.md +++ b/plugins/firecrawl/README.md @@ -1,13 +1,13 @@ -# Firecrawl API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Firecrawl API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-firecrawl`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-firecrawl`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-firecrawl`, `typescript-models`, `typescript-testing` | +# Firecrawl API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Firecrawl API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-firecrawl`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-firecrawl`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-firecrawl`, `typescript-models`, `typescript-testing` | diff --git a/plugins/firecrawl/plugin.json b/plugins/firecrawl/plugin.json index e45a2521..26a5b3b0 100644 --- a/plugins/firecrawl/plugin.json +++ b/plugins/firecrawl/plugin.json @@ -1,7 +1,7 @@ { "name": "firecrawl", "displayName": "Firecrawl API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Firecrawl API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/firecrawl/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/firecrawl/skills/dotnet/dotnet-getting-started/SKILL.md index fabdbecc..0355714b 100644 --- a/plugins/firecrawl/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/firecrawl/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Firecrawl API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-firecrawl-api is the workflow that uses it." ---- - -# Getting started with the Firecrawl API .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Firecrawl API SDK fact. `dotnet-integrate-firecrawl-api` is the workflow that drives it — you collect the contracts for the work in scope into `firecrawl-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Firecrawl API | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/firecrawl-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `FirecrawlApi` (the `using` namespace) | -| Client class | `FirecrawlApiClient` | -| Options class | `FirecrawlApiClientOptions` | -| Auth | **Bearer** token — set `BearerAuth` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://api.firecrawl.dev/v2` | -| Log environment variable | `FIRECRAWLAPICLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-firecrawl-api` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using FirecrawlApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `FirecrawlApiClient` | `FirecrawlApi` | -| `FirecrawlApiClientOptions` | `FirecrawlApi` | -| `ServerEnvironment` | `FirecrawlApi.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Firecrawl API, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/FirecrawlApi/Models/`, `src/FirecrawlApi/Errors/` and `src/FirecrawlApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/FirecrawlApi/Models/`, enums `src/FirecrawlApi/Models/Enums/`, typed errors `src/FirecrawlApi/Errors/`, request records `src/FirecrawlApi/Requests/`, or `src/FirecrawlApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `firecrawl-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/firecrawl-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/firecrawl-api-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/firecrawl-api-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/firecrawl-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\firecrawl-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/firecrawl-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `firecrawl-api-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/FirecrawlApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/FirecrawlApi/Models/` (+ `src/FirecrawlApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/FirecrawlApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/FirecrawlApi/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/FirecrawlApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/FirecrawlApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/FirecrawlApi/Servers/`, `src/FirecrawlApi/FirecrawlApiClient.cs`, `src/FirecrawlApi/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Firecrawl API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `FIRECRAWLAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/FirecrawlApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-firecrawl-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new FirecrawlApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `FirecrawlApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Firecrawl API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-firecrawl-api is the workflow that uses it." +--- + +# Getting started with the Firecrawl API .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Firecrawl API SDK fact. `dotnet-integrate-firecrawl-api` is the workflow that drives it — you collect the contracts for the work in scope into `firecrawl-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Firecrawl API | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/firecrawl-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `FirecrawlApi` (the `using` namespace) | +| Client class | `FirecrawlApiClient` | +| Options class | `FirecrawlApiClientOptions` | +| Auth | **Bearer** token — set `BearerAuth` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://api.firecrawl.dev/v2` | +| Log environment variable | `FIRECRAWLAPICLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-firecrawl-api` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using FirecrawlApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `FirecrawlApiClient` | `FirecrawlApi` | +| `FirecrawlApiClientOptions` | `FirecrawlApi` | +| `ServerEnvironment` | `FirecrawlApi.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Firecrawl API, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/FirecrawlApi/Models/`, `src/FirecrawlApi/Errors/` and `src/FirecrawlApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/FirecrawlApi/Models/`, enums `src/FirecrawlApi/Models/Enums/`, typed errors `src/FirecrawlApi/Errors/`, request records `src/FirecrawlApi/Requests/`, or `src/FirecrawlApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `firecrawl-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/firecrawl-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/firecrawl-api-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/firecrawl-api-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/firecrawl-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\firecrawl-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/firecrawl-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `firecrawl-api-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/FirecrawlApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/FirecrawlApi/Models/` (+ `src/FirecrawlApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/FirecrawlApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/FirecrawlApi/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/FirecrawlApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/FirecrawlApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/FirecrawlApi/Servers/`, `src/FirecrawlApi/FirecrawlApiClient.cs`, `src/FirecrawlApi/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Firecrawl API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `FIRECRAWLAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/FirecrawlApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-firecrawl-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new FirecrawlApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `FirecrawlApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/firecrawl/skills/python/python-getting-started/SKILL.md b/plugins/firecrawl/skills/python/python-getting-started/SKILL.md index 0e7c9a10..9707bfff 100644 --- a/plugins/firecrawl/skills/python/python-getting-started/SKILL.md +++ b/plugins/firecrawl/skills/python/python-getting-started/SKILL.md @@ -1,288 +1,288 @@ ---- -name: "python-getting-started" -description: "Firecrawl API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Firecrawl API Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Firecrawl API Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Firecrawl API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-firecrawl-api` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `firecrawl_api/` and `pyproject.toml` of the generated package at version `v2`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Firecrawl API | -| Distribution name (what you install) | `firecrawl-api` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `firecrawl_api` — not the string you install | -| Source repository | https://github.com/context-plugins/firecrawl-python-sdk | -| Source branch | `main` | -| Version | `v2` | -| Sync client class | `FirecrawlApiClient` (alias `Client`) | -| Async client class | `AsyncFirecrawlApiClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `bearer_auth`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **Bearer** token — set `bearer_auth` | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.firecrawl.dev/v2` | -| Base-URL config | `ServerConfig` (`firecrawl_api/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 52 operations across 16 controllers · 303 models · 16 unions · 85 enums · 44 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "firecrawl-api @ git+https://github.com/context-plugins/firecrawl-python-sdk.git@main" -python -c "import firecrawl_api, pathlib; print(pathlib.Path(firecrawl_api.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from firecrawl_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`firecrawl_api/__init__.py` exports exactly 5 names beside the `firecrawl_api.models` subpackage it re-exports: - -```python -from firecrawl_api import ( - AsyncClient, - AsyncFirecrawlApiClient, - Client, - FirecrawlApiClient, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `firecrawl_api.models` | -| Enums (and their open `…OrStr` aliases) | `firecrawl_api.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` · `FileInput` · `AsyncFileInput` · `NamedFile` · `BinaryContent` · `AsyncBinaryContent` · `BinaryReader` · `AsyncBinaryReader` | `firecrawl_api.core` | -| Per-operation error *unions* | `firecrawl_api.errors` (`AskSupportAgentErrorBody`, …) | - -`firecrawl_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`firecrawl_api/server/server_config.py`), and its default is **`https://api.firecrawl.dev/v2`**: - -```python -base_url: str = "https://api.firecrawl.dev/v2" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://api.firecrawl.dev/v2`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (one scheme) - -A bearer token, exposed as the client's `bearer_auth=` keyword taking a plain string. - -```python -from firecrawl_api import Client - -client = Client(bearer_auth="") -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.account` | `Account` / `AsyncAccount` | 1 | `get_activity` | -| `client.agent` | `Agent` / `AsyncAgent` | 3 | `cancel_agent` · `get_agent_status` · `start_agent` | -| `client.billing` | `Billing` / `AsyncBilling` | 4 | `get_credit_usage` · `get_historical_credit_usage` · `get_historical_token_usage` · `get_token_usage` | -| `client.crawling` | `Crawling` / `AsyncCrawling` | 6 | `cancel_crawl` · `crawl_params_preview` · `crawl_urls` · `get_active_crawls` · `get_crawl_errors` · `get_crawl_status` | -| `client.developer` | `Developer` / `AsyncDeveloper` | 2 | `developer_search` · `developer_search_post` | -| `client.extraction` | `Extraction` / `AsyncExtraction` | 2 | `extract_data` · `get_extract_status` | -| `client.feedback` | `Feedback` / `AsyncFeedback` | 2 | `submit_endpoint_feedback` · `submit_search_feedback` | -| `client.interact` | `Interact` / `AsyncInteract` | 4 | `create_browser_session` · `delete_browser_session` · `execute_browser_code` · `list_browser_sessions` | -| `client.mapping_api` | `MappingApi` / `AsyncMappingApi` | 1 | `map_urls` | -| `client.miscellaneous` | `Miscellaneous` / `AsyncMiscellaneous` | 1 | `get_queue_status` | -| `client.monitoring` | `Monitoring` / `AsyncMonitoring` | 8 | `create_monitor` · `delete_monitor` · `get_monitor` · `get_monitor_check` · `list_monitor_checks` · `list_monitors` · … | -| `client.research_api` | `ResearchApi` / `AsyncResearchApi` | 3 | `research_get_paper` · `research_related_papers` · `research_search_papers` | -| `client.scraping` | `Scraping` / `AsyncScraping` | 9 | `cancel_batch_scrape` · `get_batch_scrape_errors` · `get_batch_scrape_status` · `get_scrape_status` · `interact_with_scrape_browser_session` · `parse_file` · … | -| `client.search` | `Search` / `AsyncSearch` | 2 | `search_and_scrape` · `submit_search_feedback` | -| `client.support` | `Support` / `AsyncSupport` | 2 | `ask_support_agent` · `search_support_docs` | -| `client.threat_protection` | `ThreatProtection` / `AsyncThreatProtection` | 2 | `get_threat_protection` · `update_threat_protection` | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `firecrawl_api/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `firecrawl_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/firecrawl-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `firecrawl_api/models/actions.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `firecrawl_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import firecrawl_api, pathlib; print(pathlib.Path(firecrawl_api.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/firecrawl_api` on Windows, `.venv/lib/python3.*/site-packages/firecrawl_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| What a file or binary parameter accepts, and the reader protocols behind it (the aliases are in the import table above; not in the map's Type sources) | `core/files.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `bearer_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; a multipart body is **one flat parameter list** — every field, JSON part and file is its own parameter, and an optional part is omitted with `None` rather than by a second call shape; and the two response modes — raising vs `ApiResult` — behave differently on failure.) (*For the file positions:* load **python-file-handling** before you pass anything but a `Path` — a file position accepts far more than `bytes`, **never a `str`**, and nothing validates what you hand it.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The file positions** — `scraping.parse_file`, each annotated `FileInput` on the sync client and `AsyncFileInput` on the async one, with `list[…]` for an array part. What those admit is wider than the annotation shows and **never a `str`**, and the arm you pick decides what stays resident, who closes your handle and what the file is called on the wire — so the sheet cites **python-file-handling** for the row rather than restating it. -4. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. -5. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -6. **The `ApiError.error` union** for each operation in scope — there are **57** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `BatchScrape500Error1` — 3 operations (`scraping.cancel_batch_scrape` · `scraping.get_batch_scrape_status` · `scraping.scrape_and_extract_from_urls`); distinguishing members *none required* - 2. `Crawl500Error1` — 3 operations (`crawling.cancel_crawl` · `crawling.crawl_urls` · `crawling.get_crawl_status`); distinguishing members *none required* - 3. `FeedbackErrorResponse` — 3 operations (`feedback.submit_endpoint_feedback` · `feedback.submit_search_feedback` · `search.submit_search_feedback`); distinguishing members `success` · `error` - 4. `Interact402Error1` — 3 operations (`interact.create_browser_session` · `interact.delete_browser_session` · `interact.list_browser_sessions`); distinguishing members *none required* - 5. `BatchScrape402Error1` — 2 operations (`scraping.get_batch_scrape_status` · `scraping.scrape_and_extract_from_urls`); distinguishing members *none required* - 6. `BatchScrape429Error1` — 2 operations (`scraping.get_batch_scrape_status` · `scraping.scrape_and_extract_from_urls`); distinguishing members *none required* - 7. `Crawl402Error1` — 2 operations (`crawling.crawl_urls` · `crawling.get_crawl_status`); distinguishing members *none required* - 8. `Crawl429Error1` — 2 operations (`crawling.crawl_urls` · `crawling.get_crawl_status`); distinguishing members *none required* - 9. `ScrapeInteract403Error1` — 2 operations (`scraping.interact_with_scrape_browser_session` · `scraping.stop_interactive_scrape_browser_session`); distinguishing members *none required* - 10. `ScrapeInteract404Error1` — 2 operations (`scraping.interact_with_scrape_browser_session` · `scraping.stop_interactive_scrape_browser_session`); distinguishing members *none required* - 11. `SupportProxyErrorResponse` — 2 operations (`support.ask_support_agent` · `support.search_support_docs`); distinguishing members *none required* - 12. `Agent402Error1` — 1 operation (`agent.start_agent`); distinguishing members *none required* - 13. `Agent429Error1` — 1 operation (`agent.start_agent`); distinguishing members *none required* - 14. `BatchScrape404Error1` — 1 operation (`scraping.cancel_batch_scrape`); distinguishing members *none required* - 15. `BatchScrapeErrors402Error1` — 1 operation (`scraping.get_batch_scrape_errors`); distinguishing members *none required* - 16. `BatchScrapeErrors429Error1` — 1 operation (`scraping.get_batch_scrape_errors`); distinguishing members *none required* - 17. `BatchScrapeErrors500Error1` — 1 operation (`scraping.get_batch_scrape_errors`); distinguishing members *none required* - 18. `Crawl404Error1` — 1 operation (`crawling.cancel_crawl`); distinguishing members *none required* - 19. `CrawlActive402Error1` — 1 operation (`crawling.get_active_crawls`); distinguishing members *none required* - 20. `CrawlActive429Error1` — 1 operation (`crawling.get_active_crawls`); distinguishing members *none required* - 21. `CrawlActive500Error1` — 1 operation (`crawling.get_active_crawls`); distinguishing members *none required* - 22. `CrawlErrors402Error1` — 1 operation (`crawling.get_crawl_errors`); distinguishing members *none required* - 23. `CrawlErrors429Error1` — 1 operation (`crawling.get_crawl_errors`); distinguishing members *none required* - 24. `CrawlErrors500Error1` — 1 operation (`crawling.get_crawl_errors`); distinguishing members *none required* - 25. `CrawlParamsPreview400Error1` — 1 operation (`crawling.crawl_params_preview`); distinguishing members *none required* - 26. `CrawlParamsPreview401Error1` — 1 operation (`crawling.crawl_params_preview`); distinguishing members *none required* - 27. `CrawlParamsPreview500Error1` — 1 operation (`crawling.crawl_params_preview`); distinguishing members *none required* - 28. `Extract400Error1` — 1 operation (`extraction.extract_data`); distinguishing members *none required* - 29. `Extract500Error1` — 1 operation (`extraction.extract_data`); distinguishing members *none required* - 30. `InteractExecute402Error1` — 1 operation (`interact.execute_browser_code`); distinguishing members *none required* - 31. `Map402Error1` — 1 operation (`mapping_api.map_urls`); distinguishing members *none required* - 32. `Map429Error1` — 1 operation (`mapping_api.map_urls`); distinguishing members *none required* - 33. `Map500Error1` — 1 operation (`mapping_api.map_urls`); distinguishing members *none required* - 34. `Parse400Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* - 35. `Parse402Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* - 36. `Parse429Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* - 37. `Parse500Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* - 38. `Scrape402Error1` — 1 operation (`scraping.scrape_and_extract_from_url`); distinguishing members *none required* - 39. `Scrape402Error21` — 1 operation (`scraping.get_scrape_status`); distinguishing members *none required* - 40. `Scrape429Error1` — 1 operation (`scraping.scrape_and_extract_from_url`); distinguishing members *none required* - 41. `Scrape429Error21` — 1 operation (`scraping.get_scrape_status`); distinguishing members *none required* - 42. `Scrape500Error1` — 1 operation (`scraping.scrape_and_extract_from_url`); distinguishing members *none required* - 43. `Scrape500Error21` — 1 operation (`scraping.get_scrape_status`); distinguishing members *none required* - 44. `ScrapeInteract400Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* - 45. `ScrapeInteract402Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* - 46. `ScrapeInteract409Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* - 47. `ScrapeInteract410Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* - 48. `ScrapeInteract429Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* - 49. `ScrapeInteract502Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* - 50. `Search408Error1` — 1 operation (`search.search_and_scrape`); distinguishing members *none required* - 51. `Search500Error1` — 1 operation (`search.search_and_scrape`); distinguishing members *none required* - 52. `TeamCreditUsage404Error1` — 1 operation (`billing.get_credit_usage`); distinguishing members *none required* - 53. `TeamCreditUsage500Error1` — 1 operation (`billing.get_credit_usage`); distinguishing members *none required* - 54. `TeamCreditUsageHistorical500Error1` — 1 operation (`billing.get_historical_credit_usage`); distinguishing members *none required* - 55. `TeamTokenUsage404Error1` — 1 operation (`billing.get_token_usage`); distinguishing members *none required* - 56. `TeamTokenUsage500Error1` — 1 operation (`billing.get_token_usage`); distinguishing members *none required* - 57. `TeamTokenUsageHistorical500Error1` — 1 operation (`billing.get_historical_token_usage`); distinguishing members *none required* - 58. *(none)* — `account.get_activity` · `agent.cancel_agent` · `agent.get_agent_status` · `developer.developer_search` · `developer.developer_search_post` · `extraction.get_extract_status` · `miscellaneous.get_queue_status` · `monitoring.create_monitor` · `monitoring.delete_monitor` · `monitoring.get_monitor` · `monitoring.get_monitor_check` · `monitoring.list_monitor_checks` · `monitoring.list_monitors` · `monitoring.run_monitor` · `monitoring.update_monitor` · `research_api.research_get_paper` · `research_api.research_related_papers` · `research_api.research_search_papers` · `threat_protection.get_threat_protection` · `threat_protection.update_threat_protection`: no typed arm, so `.error` is always `RawError` - 59. So `isinstance(e.error, BatchScrape500Error1)` matches only 3 of 52 operations. -7. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 6 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -8. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -9. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -10. **Which host the `base_url` selects**, because omitting it is silently the default. -11. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Firecrawl API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Firecrawl API Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Firecrawl API Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Firecrawl API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-firecrawl-api` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `firecrawl_api/` and `pyproject.toml` of the generated package at version `v2`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Firecrawl API | +| Distribution name (what you install) | `firecrawl-api` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `firecrawl_api` — not the string you install | +| Source repository | https://github.com/context-plugins/firecrawl-python-sdk | +| Source branch | `main` | +| Version | `v2` | +| Sync client class | `FirecrawlApiClient` (alias `Client`) | +| Async client class | `AsyncFirecrawlApiClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `bearer_auth`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **Bearer** token — set `bearer_auth` | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.firecrawl.dev/v2` | +| Base-URL config | `ServerConfig` (`firecrawl_api/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 52 operations across 16 controllers · 303 models · 16 unions · 85 enums · 44 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "firecrawl-api @ git+https://github.com/context-plugins/firecrawl-python-sdk.git@main" +python -c "import firecrawl_api, pathlib; print(pathlib.Path(firecrawl_api.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from firecrawl_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`firecrawl_api/__init__.py` exports exactly 5 names beside the `firecrawl_api.models` subpackage it re-exports: + +```python +from firecrawl_api import ( + AsyncClient, + AsyncFirecrawlApiClient, + Client, + FirecrawlApiClient, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `firecrawl_api.models` | +| Enums (and their open `…OrStr` aliases) | `firecrawl_api.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` · `FileInput` · `AsyncFileInput` · `NamedFile` · `BinaryContent` · `AsyncBinaryContent` · `BinaryReader` · `AsyncBinaryReader` | `firecrawl_api.core` | +| Per-operation error *unions* | `firecrawl_api.errors` (`AskSupportAgentErrorBody`, …) | + +`firecrawl_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`firecrawl_api/server/server_config.py`), and its default is **`https://api.firecrawl.dev/v2`**: + +```python +base_url: str = "https://api.firecrawl.dev/v2" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://api.firecrawl.dev/v2`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (one scheme) + +A bearer token, exposed as the client's `bearer_auth=` keyword taking a plain string. + +```python +from firecrawl_api import Client + +client = Client(bearer_auth="") +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.account` | `Account` / `AsyncAccount` | 1 | `get_activity` | +| `client.agent` | `Agent` / `AsyncAgent` | 3 | `cancel_agent` · `get_agent_status` · `start_agent` | +| `client.billing` | `Billing` / `AsyncBilling` | 4 | `get_credit_usage` · `get_historical_credit_usage` · `get_historical_token_usage` · `get_token_usage` | +| `client.crawling` | `Crawling` / `AsyncCrawling` | 6 | `cancel_crawl` · `crawl_params_preview` · `crawl_urls` · `get_active_crawls` · `get_crawl_errors` · `get_crawl_status` | +| `client.developer` | `Developer` / `AsyncDeveloper` | 2 | `developer_search` · `developer_search_post` | +| `client.extraction` | `Extraction` / `AsyncExtraction` | 2 | `extract_data` · `get_extract_status` | +| `client.feedback` | `Feedback` / `AsyncFeedback` | 2 | `submit_endpoint_feedback` · `submit_search_feedback` | +| `client.interact` | `Interact` / `AsyncInteract` | 4 | `create_browser_session` · `delete_browser_session` · `execute_browser_code` · `list_browser_sessions` | +| `client.mapping_api` | `MappingApi` / `AsyncMappingApi` | 1 | `map_urls` | +| `client.miscellaneous` | `Miscellaneous` / `AsyncMiscellaneous` | 1 | `get_queue_status` | +| `client.monitoring` | `Monitoring` / `AsyncMonitoring` | 8 | `create_monitor` · `delete_monitor` · `get_monitor` · `get_monitor_check` · `list_monitor_checks` · `list_monitors` · … | +| `client.research_api` | `ResearchApi` / `AsyncResearchApi` | 3 | `research_get_paper` · `research_related_papers` · `research_search_papers` | +| `client.scraping` | `Scraping` / `AsyncScraping` | 9 | `cancel_batch_scrape` · `get_batch_scrape_errors` · `get_batch_scrape_status` · `get_scrape_status` · `interact_with_scrape_browser_session` · `parse_file` · … | +| `client.search` | `Search` / `AsyncSearch` | 2 | `search_and_scrape` · `submit_search_feedback` | +| `client.support` | `Support` / `AsyncSupport` | 2 | `ask_support_agent` · `search_support_docs` | +| `client.threat_protection` | `ThreatProtection` / `AsyncThreatProtection` | 2 | `get_threat_protection` · `update_threat_protection` | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `firecrawl_api/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `firecrawl_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/firecrawl-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `firecrawl_api/models/actions.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `firecrawl_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import firecrawl_api, pathlib; print(pathlib.Path(firecrawl_api.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/firecrawl_api` on Windows, `.venv/lib/python3.*/site-packages/firecrawl_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| What a file or binary parameter accepts, and the reader protocols behind it (the aliases are in the import table above; not in the map's Type sources) | `core/files.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `bearer_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; a multipart body is **one flat parameter list** — every field, JSON part and file is its own parameter, and an optional part is omitted with `None` rather than by a second call shape; and the two response modes — raising vs `ApiResult` — behave differently on failure.) (*For the file positions:* load **python-file-handling** before you pass anything but a `Path` — a file position accepts far more than `bytes`, **never a `str`**, and nothing validates what you hand it.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The file positions** — `scraping.parse_file`, each annotated `FileInput` on the sync client and `AsyncFileInput` on the async one, with `list[…]` for an array part. What those admit is wider than the annotation shows and **never a `str`**, and the arm you pick decides what stays resident, who closes your handle and what the file is called on the wire — so the sheet cites **python-file-handling** for the row rather than restating it. +4. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. +5. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +6. **The `ApiError.error` union** for each operation in scope — there are **57** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `BatchScrape500Error1` — 3 operations (`scraping.cancel_batch_scrape` · `scraping.get_batch_scrape_status` · `scraping.scrape_and_extract_from_urls`); distinguishing members *none required* + 2. `Crawl500Error1` — 3 operations (`crawling.cancel_crawl` · `crawling.crawl_urls` · `crawling.get_crawl_status`); distinguishing members *none required* + 3. `FeedbackErrorResponse` — 3 operations (`feedback.submit_endpoint_feedback` · `feedback.submit_search_feedback` · `search.submit_search_feedback`); distinguishing members `success` · `error` + 4. `Interact402Error1` — 3 operations (`interact.create_browser_session` · `interact.delete_browser_session` · `interact.list_browser_sessions`); distinguishing members *none required* + 5. `BatchScrape402Error1` — 2 operations (`scraping.get_batch_scrape_status` · `scraping.scrape_and_extract_from_urls`); distinguishing members *none required* + 6. `BatchScrape429Error1` — 2 operations (`scraping.get_batch_scrape_status` · `scraping.scrape_and_extract_from_urls`); distinguishing members *none required* + 7. `Crawl402Error1` — 2 operations (`crawling.crawl_urls` · `crawling.get_crawl_status`); distinguishing members *none required* + 8. `Crawl429Error1` — 2 operations (`crawling.crawl_urls` · `crawling.get_crawl_status`); distinguishing members *none required* + 9. `ScrapeInteract403Error1` — 2 operations (`scraping.interact_with_scrape_browser_session` · `scraping.stop_interactive_scrape_browser_session`); distinguishing members *none required* + 10. `ScrapeInteract404Error1` — 2 operations (`scraping.interact_with_scrape_browser_session` · `scraping.stop_interactive_scrape_browser_session`); distinguishing members *none required* + 11. `SupportProxyErrorResponse` — 2 operations (`support.ask_support_agent` · `support.search_support_docs`); distinguishing members *none required* + 12. `Agent402Error1` — 1 operation (`agent.start_agent`); distinguishing members *none required* + 13. `Agent429Error1` — 1 operation (`agent.start_agent`); distinguishing members *none required* + 14. `BatchScrape404Error1` — 1 operation (`scraping.cancel_batch_scrape`); distinguishing members *none required* + 15. `BatchScrapeErrors402Error1` — 1 operation (`scraping.get_batch_scrape_errors`); distinguishing members *none required* + 16. `BatchScrapeErrors429Error1` — 1 operation (`scraping.get_batch_scrape_errors`); distinguishing members *none required* + 17. `BatchScrapeErrors500Error1` — 1 operation (`scraping.get_batch_scrape_errors`); distinguishing members *none required* + 18. `Crawl404Error1` — 1 operation (`crawling.cancel_crawl`); distinguishing members *none required* + 19. `CrawlActive402Error1` — 1 operation (`crawling.get_active_crawls`); distinguishing members *none required* + 20. `CrawlActive429Error1` — 1 operation (`crawling.get_active_crawls`); distinguishing members *none required* + 21. `CrawlActive500Error1` — 1 operation (`crawling.get_active_crawls`); distinguishing members *none required* + 22. `CrawlErrors402Error1` — 1 operation (`crawling.get_crawl_errors`); distinguishing members *none required* + 23. `CrawlErrors429Error1` — 1 operation (`crawling.get_crawl_errors`); distinguishing members *none required* + 24. `CrawlErrors500Error1` — 1 operation (`crawling.get_crawl_errors`); distinguishing members *none required* + 25. `CrawlParamsPreview400Error1` — 1 operation (`crawling.crawl_params_preview`); distinguishing members *none required* + 26. `CrawlParamsPreview401Error1` — 1 operation (`crawling.crawl_params_preview`); distinguishing members *none required* + 27. `CrawlParamsPreview500Error1` — 1 operation (`crawling.crawl_params_preview`); distinguishing members *none required* + 28. `Extract400Error1` — 1 operation (`extraction.extract_data`); distinguishing members *none required* + 29. `Extract500Error1` — 1 operation (`extraction.extract_data`); distinguishing members *none required* + 30. `InteractExecute402Error1` — 1 operation (`interact.execute_browser_code`); distinguishing members *none required* + 31. `Map402Error1` — 1 operation (`mapping_api.map_urls`); distinguishing members *none required* + 32. `Map429Error1` — 1 operation (`mapping_api.map_urls`); distinguishing members *none required* + 33. `Map500Error1` — 1 operation (`mapping_api.map_urls`); distinguishing members *none required* + 34. `Parse400Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* + 35. `Parse402Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* + 36. `Parse429Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* + 37. `Parse500Error1` — 1 operation (`scraping.parse_file`); distinguishing members *none required* + 38. `Scrape402Error1` — 1 operation (`scraping.scrape_and_extract_from_url`); distinguishing members *none required* + 39. `Scrape402Error21` — 1 operation (`scraping.get_scrape_status`); distinguishing members *none required* + 40. `Scrape429Error1` — 1 operation (`scraping.scrape_and_extract_from_url`); distinguishing members *none required* + 41. `Scrape429Error21` — 1 operation (`scraping.get_scrape_status`); distinguishing members *none required* + 42. `Scrape500Error1` — 1 operation (`scraping.scrape_and_extract_from_url`); distinguishing members *none required* + 43. `Scrape500Error21` — 1 operation (`scraping.get_scrape_status`); distinguishing members *none required* + 44. `ScrapeInteract400Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* + 45. `ScrapeInteract402Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* + 46. `ScrapeInteract409Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* + 47. `ScrapeInteract410Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* + 48. `ScrapeInteract429Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* + 49. `ScrapeInteract502Error1` — 1 operation (`scraping.interact_with_scrape_browser_session`); distinguishing members *none required* + 50. `Search408Error1` — 1 operation (`search.search_and_scrape`); distinguishing members *none required* + 51. `Search500Error1` — 1 operation (`search.search_and_scrape`); distinguishing members *none required* + 52. `TeamCreditUsage404Error1` — 1 operation (`billing.get_credit_usage`); distinguishing members *none required* + 53. `TeamCreditUsage500Error1` — 1 operation (`billing.get_credit_usage`); distinguishing members *none required* + 54. `TeamCreditUsageHistorical500Error1` — 1 operation (`billing.get_historical_credit_usage`); distinguishing members *none required* + 55. `TeamTokenUsage404Error1` — 1 operation (`billing.get_token_usage`); distinguishing members *none required* + 56. `TeamTokenUsage500Error1` — 1 operation (`billing.get_token_usage`); distinguishing members *none required* + 57. `TeamTokenUsageHistorical500Error1` — 1 operation (`billing.get_historical_token_usage`); distinguishing members *none required* + 58. *(none)* — `account.get_activity` · `agent.cancel_agent` · `agent.get_agent_status` · `developer.developer_search` · `developer.developer_search_post` · `extraction.get_extract_status` · `miscellaneous.get_queue_status` · `monitoring.create_monitor` · `monitoring.delete_monitor` · `monitoring.get_monitor` · `monitoring.get_monitor_check` · `monitoring.list_monitor_checks` · `monitoring.list_monitors` · `monitoring.run_monitor` · `monitoring.update_monitor` · `research_api.research_get_paper` · `research_api.research_related_papers` · `research_api.research_search_papers` · `threat_protection.get_threat_protection` · `threat_protection.update_threat_protection`: no typed arm, so `.error` is always `RawError` + 59. So `isinstance(e.error, BatchScrape500Error1)` matches only 3 of 52 operations. +7. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 6 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +8. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +9. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +10. **Which host the `base_url` selects**, because omitting it is silently the default. +11. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/frankfurter/.claude-plugin/plugin.json b/plugins/frankfurter/.claude-plugin/plugin.json index 26e5fa82..b37df249 100644 --- a/plugins/frankfurter/.claude-plugin/plugin.json +++ b/plugins/frankfurter/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "frankfurter", "displayName": "Frankfurter API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Frankfurter API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/frankfurter/.cursor-plugin/plugin.json b/plugins/frankfurter/.cursor-plugin/plugin.json index 26e5fa82..b37df249 100644 --- a/plugins/frankfurter/.cursor-plugin/plugin.json +++ b/plugins/frankfurter/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "frankfurter", "displayName": "Frankfurter API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Frankfurter API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/frankfurter/README.md b/plugins/frankfurter/README.md index b02814ca..5784248c 100644 --- a/plugins/frankfurter/README.md +++ b/plugins/frankfurter/README.md @@ -1,13 +1,13 @@ -# Frankfurter API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Frankfurter API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-frankfurter`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-frankfurter`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-frankfurter`, `typescript-models`, `typescript-testing` | +# Frankfurter API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Frankfurter API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-frankfurter`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-frankfurter`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-frankfurter`, `typescript-models`, `typescript-testing` | diff --git a/plugins/frankfurter/plugin.json b/plugins/frankfurter/plugin.json index 26e5fa82..b37df249 100644 --- a/plugins/frankfurter/plugin.json +++ b/plugins/frankfurter/plugin.json @@ -1,7 +1,7 @@ { "name": "frankfurter", "displayName": "Frankfurter API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Frankfurter API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/frankfurter/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/frankfurter/skills/dotnet/dotnet-getting-started/SKILL.md index f8751170..aec0727a 100644 --- a/plugins/frankfurter/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/frankfurter/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Frankfurter API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-frankfurter-api is the workflow that uses it." ---- - -# Getting started with the Frankfurter API .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Frankfurter API SDK fact. `dotnet-integrate-frankfurter-api` is the workflow that drives it — you collect the contracts for the work in scope into `frankfurter-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Frankfurter API | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/frankfurter-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `FrankfurterApi` (the `using` namespace) | -| Client class | `FrankfurterApiClient` | -| Options class | `FrankfurterApiClientOptions` | -| Auth | None — this SDK generates no authentication | -| Environments | `ServerEnvironment.Production` *(default)* → `https://api.frankfurter.dev/v2` | -| Log environment variable | `FRANKFURTERAPICLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-frankfurter-api` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using FrankfurterApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `FrankfurterApiClient` | `FrankfurterApi` | -| `FrankfurterApiClientOptions` | `FrankfurterApi` | -| `ServerEnvironment` | `FrankfurterApi.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Frankfurter API, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/FrankfurterApi/Models/`, `src/FrankfurterApi/Errors/` and `src/FrankfurterApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/FrankfurterApi/Models/`, enums `src/FrankfurterApi/Models/Enums/`, typed errors `src/FrankfurterApi/Errors/`, request records `src/FrankfurterApi/Requests/`, or `src/FrankfurterApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `frankfurter-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/frankfurter-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/frankfurter-api-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/frankfurter-api-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/frankfurter-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\frankfurter-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/frankfurter-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `frankfurter-api-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/FrankfurterApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/FrankfurterApi/Models/` (+ `src/FrankfurterApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/FrankfurterApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/FrankfurterApi/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/FrankfurterApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/FrankfurterApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/FrankfurterApi/Servers/`, `src/FrankfurterApi/FrankfurterApiClient.cs`, `src/FrankfurterApi/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Frankfurter API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `FRANKFURTERAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/FrankfurterApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-frankfurter-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new FrankfurterApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `FrankfurterApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Frankfurter API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-frankfurter-api is the workflow that uses it." +--- + +# Getting started with the Frankfurter API .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Frankfurter API SDK fact. `dotnet-integrate-frankfurter-api` is the workflow that drives it — you collect the contracts for the work in scope into `frankfurter-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Frankfurter API | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/frankfurter-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `FrankfurterApi` (the `using` namespace) | +| Client class | `FrankfurterApiClient` | +| Options class | `FrankfurterApiClientOptions` | +| Auth | None — this SDK generates no authentication | +| Environments | `ServerEnvironment.Production` *(default)* → `https://api.frankfurter.dev/v2` | +| Log environment variable | `FRANKFURTERAPICLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-frankfurter-api` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using FrankfurterApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `FrankfurterApiClient` | `FrankfurterApi` | +| `FrankfurterApiClientOptions` | `FrankfurterApi` | +| `ServerEnvironment` | `FrankfurterApi.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Frankfurter API, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/FrankfurterApi/Models/`, `src/FrankfurterApi/Errors/` and `src/FrankfurterApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/FrankfurterApi/Models/`, enums `src/FrankfurterApi/Models/Enums/`, typed errors `src/FrankfurterApi/Errors/`, request records `src/FrankfurterApi/Requests/`, or `src/FrankfurterApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `frankfurter-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/frankfurter-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/frankfurter-api-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/frankfurter-api-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/frankfurter-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\frankfurter-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/frankfurter-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `frankfurter-api-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/FrankfurterApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/FrankfurterApi/Models/` (+ `src/FrankfurterApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/FrankfurterApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/FrankfurterApi/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/FrankfurterApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/FrankfurterApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/FrankfurterApi/Servers/`, `src/FrankfurterApi/FrankfurterApiClient.cs`, `src/FrankfurterApi/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Frankfurter API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `FRANKFURTERAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/FrankfurterApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-frankfurter-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new FrankfurterApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `FrankfurterApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/frankfurter/skills/python/python-getting-started/SKILL.md b/plugins/frankfurter/skills/python/python-getting-started/SKILL.md index a0ef51d7..fef4fe3b 100644 --- a/plugins/frankfurter/skills/python/python-getting-started/SKILL.md +++ b/plugins/frankfurter/skills/python/python-getting-started/SKILL.md @@ -1,194 +1,194 @@ ---- -name: "python-getting-started" -description: "Frankfurter API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Frankfurter API Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Frankfurter API Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Frankfurter API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-frankfurter-api` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `frankfurter_api/` and `pyproject.toml` of the generated package at version `2.1.1`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Frankfurter API | -| Distribution name (what you install) | `frankfurter-api` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `frankfurter_api` — not the string you install | -| Source repository | https://github.com/context-plugins/frankfurter-python-sdk | -| Source branch | `main` | -| Version | `2.1.1` | -| Sync client class | `FrankfurterApiClient` (alias `Client`) | -| Async client class | `AsyncFrankfurterApiClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | None — the spec declares no security scheme | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.frankfurter.dev/v2` | -| Base-URL config | `ServerConfig` (`frankfurter_api/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 5 operations, all directly on the client — no controller groups; the map's single page is `map/operations/client.md` · 12 models · 0 unions · 4 enums · 3 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "frankfurter-api @ git+https://github.com/context-plugins/frankfurter-python-sdk.git@main" -python -c "import frankfurter_api, pathlib; print(pathlib.Path(frankfurter_api.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from frankfurter_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`frankfurter_api/__init__.py` exports exactly 5 names beside the `frankfurter_api.models` subpackage it re-exports: - -```python -from frankfurter_api import ( - AsyncClient, - AsyncFrankfurterApiClient, - Client, - FrankfurterApiClient, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `frankfurter_api.models` | -| Enums (and their open `…OrStr` aliases) | `frankfurter_api.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `frankfurter_api.core` | -| Per-operation error *unions* | `frankfurter_api.errors` (`GetCurrencyErrorBody`, …) | - -`frankfurter_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`frankfurter_api/server/server_config.py`), and its default is **`https://api.frankfurter.dev/v2`**: - -```python -base_url: str = "https://api.frankfurter.dev/v2" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://api.frankfurter.dev/v2`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern — none declared - -The API declares no security scheme, so the client takes no credentials keyword and every request goes out unauthenticated. If the provider nonetheless requires a credential, pass it yourself as a header through `request_options` — see `python-calling-endpoints`. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `frankfurter_api/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `frankfurter_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/frankfurter-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `frankfurter_api/models/currency.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `frankfurter_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import frankfurter_api, pathlib; print(pathlib.Path(frankfurter_api.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/frankfurter_api` on Windows, `.venv/lib/python3.*/site-packages/frankfurter_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `client.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. This SDK declares no scheme, so there is no credentials keyword to set. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — there are **three** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `NotFound1` — 3 operations (`get_currency` · `get_rate` · `get_rates`); distinguishing members *none required* - 2. `ServiceUnavailable1` — 2 operations (`get_rate` · `get_rates`); distinguishing members *none required* - 3. `UnprocessableEntity1` — 2 operations (`get_rate` · `get_rates`); distinguishing members *none required* - 4. *(none)* — `get_currencies` · `get_providers`: no typed arm, so `.error` is always `RawError` - 5. So `isinstance(e.error, NotFound1)` matches only 3 of 5 operations. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 5 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which host the `base_url` selects**, because omitting it is silently the default. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Frankfurter API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Frankfurter API Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Frankfurter API Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Frankfurter API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-frankfurter-api` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `frankfurter_api/` and `pyproject.toml` of the generated package at version `2.1.1`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Frankfurter API | +| Distribution name (what you install) | `frankfurter-api` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `frankfurter_api` — not the string you install | +| Source repository | https://github.com/context-plugins/frankfurter-python-sdk | +| Source branch | `main` | +| Version | `2.1.1` | +| Sync client class | `FrankfurterApiClient` (alias `Client`) | +| Async client class | `AsyncFrankfurterApiClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | None — the spec declares no security scheme | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.frankfurter.dev/v2` | +| Base-URL config | `ServerConfig` (`frankfurter_api/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 5 operations, all directly on the client — no controller groups; the map's single page is `map/operations/client.md` · 12 models · 0 unions · 4 enums · 3 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "frankfurter-api @ git+https://github.com/context-plugins/frankfurter-python-sdk.git@main" +python -c "import frankfurter_api, pathlib; print(pathlib.Path(frankfurter_api.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from frankfurter_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`frankfurter_api/__init__.py` exports exactly 5 names beside the `frankfurter_api.models` subpackage it re-exports: + +```python +from frankfurter_api import ( + AsyncClient, + AsyncFrankfurterApiClient, + Client, + FrankfurterApiClient, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `frankfurter_api.models` | +| Enums (and their open `…OrStr` aliases) | `frankfurter_api.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `frankfurter_api.core` | +| Per-operation error *unions* | `frankfurter_api.errors` (`GetCurrencyErrorBody`, …) | + +`frankfurter_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`frankfurter_api/server/server_config.py`), and its default is **`https://api.frankfurter.dev/v2`**: + +```python +base_url: str = "https://api.frankfurter.dev/v2" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://api.frankfurter.dev/v2`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern — none declared + +The API declares no security scheme, so the client takes no credentials keyword and every request goes out unauthenticated. If the provider nonetheless requires a credential, pass it yourself as a header through `request_options` — see `python-calling-endpoints`. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `frankfurter_api/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `frankfurter_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/frankfurter-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `frankfurter_api/models/currency.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `frankfurter_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import frankfurter_api, pathlib; print(pathlib.Path(frankfurter_api.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/frankfurter_api` on Windows, `.venv/lib/python3.*/site-packages/frankfurter_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `client.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. This SDK declares no scheme, so there is no credentials keyword to set. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — there are **three** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `NotFound1` — 3 operations (`get_currency` · `get_rate` · `get_rates`); distinguishing members *none required* + 2. `ServiceUnavailable1` — 2 operations (`get_rate` · `get_rates`); distinguishing members *none required* + 3. `UnprocessableEntity1` — 2 operations (`get_rate` · `get_rates`); distinguishing members *none required* + 4. *(none)* — `get_currencies` · `get_providers`: no typed arm, so `.error` is always `RawError` + 5. So `isinstance(e.error, NotFound1)` matches only 3 of 5 operations. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 5 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which host the `base_url` selects**, because omitting it is silently the default. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/paypal/.claude-plugin/plugin.json b/plugins/paypal/.claude-plugin/plugin.json index 8345bdae..687020b9 100644 --- a/plugins/paypal/.claude-plugin/plugin.json +++ b/plugins/paypal/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "paypal", "displayName": "Paypal", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated PayPal Server SDK SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/paypal/.cursor-plugin/plugin.json b/plugins/paypal/.cursor-plugin/plugin.json index 8345bdae..687020b9 100644 --- a/plugins/paypal/.cursor-plugin/plugin.json +++ b/plugins/paypal/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "paypal", "displayName": "Paypal", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated PayPal Server SDK SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/paypal/plugin.json b/plugins/paypal/plugin.json index 8345bdae..687020b9 100644 --- a/plugins/paypal/plugin.json +++ b/plugins/paypal/plugin.json @@ -1,7 +1,7 @@ { "name": "paypal", "displayName": "Paypal", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated PayPal Server SDK SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/paypal/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/paypal/skills/dotnet/dotnet-getting-started/SKILL.md index 97d6849c..8124a9ce 100644 --- a/plugins/paypal/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/paypal/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Paypal .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-paypal is the workflow that uses it." ---- - -# Getting started with the Paypal .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Paypal SDK fact. `dotnet-integrate-paypal` is the workflow that drives it — you collect the contracts for the work in scope into `paypal-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Paypal | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/paypal-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `Paypal` (the `using` namespace) | -| Client class | `PaypalClient` | -| Options class | `PaypalClientOptions` | -| Auth | **OAuth 2.0** client credentials — set `Oauth2` | -| Environments | `ServerEnvironment.Sandbox` *(default)* → `https://api-m.sandbox.paypal.com` | -| Log environment variable | `PAYPALCLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-paypal` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using Paypal.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `PaypalClient` | `Paypal` | -| `PaypalClientOptions` | `Paypal` | -| `ServerEnvironment` | `Paypal.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Paypal, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/Paypal/Models/`, `src/Paypal/Errors/` and `src/Paypal/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/Paypal/Models/`, enums `src/Paypal/Models/Enums/`, typed errors `src/Paypal/Errors/`, request records `src/Paypal/Requests/`, or `src/Paypal/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `paypal-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/paypal-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/paypal-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/paypal-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/paypal-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\paypal-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/paypal-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `paypal-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/Paypal/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/Paypal/Models/` (+ `src/Paypal/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/Paypal/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/Paypal/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/Paypal/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/Paypal/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/Paypal/Servers/`, `src/Paypal/PaypalClient.cs`, `src/Paypal/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Paypal may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `PAYPALCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/Paypal/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-paypal`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new PaypalClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `PaypalClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Paypal .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-paypal is the workflow that uses it." +--- + +# Getting started with the Paypal .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Paypal SDK fact. `dotnet-integrate-paypal` is the workflow that drives it — you collect the contracts for the work in scope into `paypal-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Paypal | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/paypal-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `Paypal` (the `using` namespace) | +| Client class | `PaypalClient` | +| Options class | `PaypalClientOptions` | +| Auth | **OAuth 2.0** client credentials — set `Oauth2` | +| Environments | `ServerEnvironment.Sandbox` *(default)* → `https://api-m.sandbox.paypal.com` | +| Log environment variable | `PAYPALCLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-paypal` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using Paypal.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `PaypalClient` | `Paypal` | +| `PaypalClientOptions` | `Paypal` | +| `ServerEnvironment` | `Paypal.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Paypal, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/Paypal/Models/`, `src/Paypal/Errors/` and `src/Paypal/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/Paypal/Models/`, enums `src/Paypal/Models/Enums/`, typed errors `src/Paypal/Errors/`, request records `src/Paypal/Requests/`, or `src/Paypal/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `paypal-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/paypal-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/paypal-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/paypal-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/paypal-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\paypal-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/paypal-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `paypal-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/Paypal/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/Paypal/Models/` (+ `src/Paypal/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/Paypal/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/Paypal/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/Paypal/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/Paypal/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/Paypal/Servers/`, `src/Paypal/PaypalClient.cs`, `src/Paypal/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Paypal may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `PAYPALCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/Paypal/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-paypal`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new PaypalClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `PaypalClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/paypal/skills/python/python-getting-started/SKILL.md b/plugins/paypal/skills/python/python-getting-started/SKILL.md index 7f37ec63..abca9679 100644 --- a/plugins/paypal/skills/python/python-getting-started/SKILL.md +++ b/plugins/paypal/skills/python/python-getting-started/SKILL.md @@ -1,224 +1,224 @@ ---- -name: "python-getting-started" -description: "Paypal Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Paypal Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Paypal Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Paypal Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-paypal` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `paypal/` and `pyproject.toml` of the generated package at version `2.29`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Paypal | -| Distribution name (what you install) | `paypal` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `paypal` — the same string you install | -| Source repository | https://github.com/context-plugins/paypal-python-sdk | -| Source branch | `main` | -| Version | `2.29` | -| Sync client class | `PaypalClient` (alias `Client`) | -| Async client class | `AsyncPaypalClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `oauth2` · `oauth2_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **OAuth 2.0** client credentials — set `oauth2` | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api-m.sandbox.paypal.com` | -| Base-URL config | `ServerConfig` (`paypal/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 40 operations across 5 controllers · 284 models · 0 unions · 87 enums · 39 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "paypal @ git+https://github.com/context-plugins/paypal-python-sdk.git@main" -python -c "import paypal, pathlib; print(pathlib.Path(paypal.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from paypal import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`paypal/__init__.py` exports exactly 5 names beside the `paypal.models` subpackage it re-exports: - -```python -from paypal import ( - AsyncClient, - AsyncPaypalClient, - Client, - PaypalClient, - ServerConfig, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `paypal.models` | -| Enums (and their open `…OrStr` aliases) | `paypal.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `ClientCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `paypal.core` | -| Per-operation error *unions* | `paypal.errors` (`ActivateBillingPlanErrorBody`, …) | - -`paypal.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`paypal/server/server_config.py`), and its default is **`https://api-m.sandbox.paypal.com`**: - -```python -base_url: str = "https://api-m.sandbox.paypal.com" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://api-m.sandbox.paypal.com`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- The token endpoint is derived from the same base URL (`/v1/oauth2/token`), so it moves only with `base_url` — you never configure it separately. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (one scheme) - -OAuth 2.0 client credentials, exposed as the client's `oauth2=` keyword taking `ClientCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/v1/oauth2/token`. - -```python -from paypal import Client -from paypal.core import ClientCredentials - -client = Client(oauth2=ClientCredentials(client_id="…", client_secret="…")) -client = Client(oauth2={"client_id": "…", "client_secret": "…"}) # equivalent -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.orders` | `Orders` / `AsyncOrders` | 8 | `authorize_order` · `capture_order` · `confirm_order` · `create_order` · `create_order_tracking` · `get_order` · … | -| `client.payments` | `Payments` / `AsyncPayments` | 7 | `capture_authorized_payment` · `get_authorized_payment` · `get_captured_payment` · `get_refund` · `reauthorize_payment` · `refund_captured_payment` · … | -| `client.subscriptions` | `Subscriptions` / `AsyncSubscriptions` | 17 | `activate_billing_plan` · `activate_subscription` · `cancel_subscription` · `capture_subscription` · `create_billing_plan` · `create_subscription` · … | -| `client.transaction_search` | `TransactionSearch` / `AsyncTransactionSearch` | 2 | `search_balances` · `search_transactions` | -| `client.vault` | `Vault` / `AsyncVault` | 6 | `create_payment_token` · `create_setup_token` · `delete_payment_token` · `get_payment_token` · `get_setup_token` · `list_customer_payment_tokens` | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `paypal/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `paypal/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/paypal-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `paypal/models/activate_subscription_request.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `paypal/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import paypal, pathlib; print(pathlib.Path(paypal.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/paypal` on Windows, `.venv/lib/python3.*/site-packages/paypal` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `oauth2=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **11 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 11 operations that return `None`** — `orders.patch_order` · `orders.update_order_tracking` · `subscriptions.activate_billing_plan` · `subscriptions.activate_subscription` · `subscriptions.cancel_subscription` · `subscriptions.deactivate_billing_plan` · `subscriptions.patch_billing_plan` · `subscriptions.patch_subscription` · `subscriptions.suspend_subscription` · `subscriptions.update_billing_plan_pricing_schemes` · `vault.delete_payment_token`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — there are **three** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `Error` — 21 operations (`orders.authorize_order` · `orders.capture_order` · `orders.confirm_order` · `orders.create_order` · `orders.create_order_tracking` · `orders.get_order` · `orders.patch_order` · `orders.update_order_tracking` · `payments.capture_authorized_payment` · `payments.get_authorized_payment` · `payments.get_captured_payment` · `payments.get_refund` · `payments.reauthorize_payment` · `payments.refund_captured_payment` · `payments.void_payment` · `vault.create_payment_token` · `vault.create_setup_token` · `vault.delete_payment_token` · `vault.get_payment_token` · `vault.get_setup_token` · `vault.list_customer_payment_tokens`); distinguishing members `name` · `message` · `debug_id` - 2. `SubscriptionError` — 17 operations (`subscriptions.activate_billing_plan` · `subscriptions.activate_subscription` · `subscriptions.cancel_subscription` · `subscriptions.capture_subscription` · `subscriptions.create_billing_plan` · `subscriptions.create_subscription` · `subscriptions.deactivate_billing_plan` · `subscriptions.get_billing_plan` · `subscriptions.get_subscription` · `subscriptions.list_billing_plans` · `subscriptions.list_subscription_transactions` · `subscriptions.list_subscriptions` · `subscriptions.patch_billing_plan` · `subscriptions.patch_subscription` · `subscriptions.revise_subscription` · `subscriptions.suspend_subscription` · `subscriptions.update_billing_plan_pricing_schemes`); distinguishing members `name` · `message` · `debug_id` - 3. `DefaultError` — 1 operation (`transaction_search.search_balances`); distinguishing members `name` · `message` · `debug_id` - 4. *(none)* — `transaction_search.search_transactions`: no typed arm, so `.error` is always `RawError` - 5. So `isinstance(e.error, Error)` matches only 21 of 40 operations. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 1 return type, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which host the `base_url` selects**, because omitting it is silently the default. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Paypal Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Paypal Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Paypal Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Paypal Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-paypal` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `paypal/` and `pyproject.toml` of the generated package at version `2.29`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Paypal | +| Distribution name (what you install) | `paypal` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `paypal` — the same string you install | +| Source repository | https://github.com/context-plugins/paypal-python-sdk | +| Source branch | `main` | +| Version | `2.29` | +| Sync client class | `PaypalClient` (alias `Client`) | +| Async client class | `AsyncPaypalClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `oauth2` · `oauth2_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **OAuth 2.0** client credentials — set `oauth2` | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api-m.sandbox.paypal.com` | +| Base-URL config | `ServerConfig` (`paypal/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 40 operations across 5 controllers · 284 models · 0 unions · 87 enums · 39 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "paypal @ git+https://github.com/context-plugins/paypal-python-sdk.git@main" +python -c "import paypal, pathlib; print(pathlib.Path(paypal.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from paypal import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`paypal/__init__.py` exports exactly 5 names beside the `paypal.models` subpackage it re-exports: + +```python +from paypal import ( + AsyncClient, + AsyncPaypalClient, + Client, + PaypalClient, + ServerConfig, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `paypal.models` | +| Enums (and their open `…OrStr` aliases) | `paypal.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `ClientCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `paypal.core` | +| Per-operation error *unions* | `paypal.errors` (`ActivateBillingPlanErrorBody`, …) | + +`paypal.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`paypal/server/server_config.py`), and its default is **`https://api-m.sandbox.paypal.com`**: + +```python +base_url: str = "https://api-m.sandbox.paypal.com" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://api-m.sandbox.paypal.com`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- The token endpoint is derived from the same base URL (`/v1/oauth2/token`), so it moves only with `base_url` — you never configure it separately. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (one scheme) + +OAuth 2.0 client credentials, exposed as the client's `oauth2=` keyword taking `ClientCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/v1/oauth2/token`. + +```python +from paypal import Client +from paypal.core import ClientCredentials + +client = Client(oauth2=ClientCredentials(client_id="…", client_secret="…")) +client = Client(oauth2={"client_id": "…", "client_secret": "…"}) # equivalent +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.orders` | `Orders` / `AsyncOrders` | 8 | `authorize_order` · `capture_order` · `confirm_order` · `create_order` · `create_order_tracking` · `get_order` · … | +| `client.payments` | `Payments` / `AsyncPayments` | 7 | `capture_authorized_payment` · `get_authorized_payment` · `get_captured_payment` · `get_refund` · `reauthorize_payment` · `refund_captured_payment` · … | +| `client.subscriptions` | `Subscriptions` / `AsyncSubscriptions` | 17 | `activate_billing_plan` · `activate_subscription` · `cancel_subscription` · `capture_subscription` · `create_billing_plan` · `create_subscription` · … | +| `client.transaction_search` | `TransactionSearch` / `AsyncTransactionSearch` | 2 | `search_balances` · `search_transactions` | +| `client.vault` | `Vault` / `AsyncVault` | 6 | `create_payment_token` · `create_setup_token` · `delete_payment_token` · `get_payment_token` · `get_setup_token` · `list_customer_payment_tokens` | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `paypal/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `paypal/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/paypal-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `paypal/models/activate_subscription_request.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `paypal/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import paypal, pathlib; print(pathlib.Path(paypal.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/paypal` on Windows, `.venv/lib/python3.*/site-packages/paypal` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `oauth2=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **11 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 11 operations that return `None`** — `orders.patch_order` · `orders.update_order_tracking` · `subscriptions.activate_billing_plan` · `subscriptions.activate_subscription` · `subscriptions.cancel_subscription` · `subscriptions.deactivate_billing_plan` · `subscriptions.patch_billing_plan` · `subscriptions.patch_subscription` · `subscriptions.suspend_subscription` · `subscriptions.update_billing_plan_pricing_schemes` · `vault.delete_payment_token`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — there are **three** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `Error` — 21 operations (`orders.authorize_order` · `orders.capture_order` · `orders.confirm_order` · `orders.create_order` · `orders.create_order_tracking` · `orders.get_order` · `orders.patch_order` · `orders.update_order_tracking` · `payments.capture_authorized_payment` · `payments.get_authorized_payment` · `payments.get_captured_payment` · `payments.get_refund` · `payments.reauthorize_payment` · `payments.refund_captured_payment` · `payments.void_payment` · `vault.create_payment_token` · `vault.create_setup_token` · `vault.delete_payment_token` · `vault.get_payment_token` · `vault.get_setup_token` · `vault.list_customer_payment_tokens`); distinguishing members `name` · `message` · `debug_id` + 2. `SubscriptionError` — 17 operations (`subscriptions.activate_billing_plan` · `subscriptions.activate_subscription` · `subscriptions.cancel_subscription` · `subscriptions.capture_subscription` · `subscriptions.create_billing_plan` · `subscriptions.create_subscription` · `subscriptions.deactivate_billing_plan` · `subscriptions.get_billing_plan` · `subscriptions.get_subscription` · `subscriptions.list_billing_plans` · `subscriptions.list_subscription_transactions` · `subscriptions.list_subscriptions` · `subscriptions.patch_billing_plan` · `subscriptions.patch_subscription` · `subscriptions.revise_subscription` · `subscriptions.suspend_subscription` · `subscriptions.update_billing_plan_pricing_schemes`); distinguishing members `name` · `message` · `debug_id` + 3. `DefaultError` — 1 operation (`transaction_search.search_balances`); distinguishing members `name` · `message` · `debug_id` + 4. *(none)* — `transaction_search.search_transactions`: no typed arm, so `.error` is always `RawError` + 5. So `isinstance(e.error, Error)` matches only 21 of 40 operations. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 1 return type, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which host the `base_url` selects**, because omitting it is silently the default. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/shutterstock/.claude-plugin/plugin.json b/plugins/shutterstock/.claude-plugin/plugin.json index 0cb3be28..06e79662 100644 --- a/plugins/shutterstock/.claude-plugin/plugin.json +++ b/plugins/shutterstock/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "shutterstock", "displayName": "Shutterstock API Explorer", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated Shutterstock API Explorer SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/shutterstock/.cursor-plugin/plugin.json b/plugins/shutterstock/.cursor-plugin/plugin.json index 0cb3be28..06e79662 100644 --- a/plugins/shutterstock/.cursor-plugin/plugin.json +++ b/plugins/shutterstock/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "shutterstock", "displayName": "Shutterstock API Explorer", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated Shutterstock API Explorer SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/shutterstock/plugin.json b/plugins/shutterstock/plugin.json index 0cb3be28..06e79662 100644 --- a/plugins/shutterstock/plugin.json +++ b/plugins/shutterstock/plugin.json @@ -1,7 +1,7 @@ { "name": "shutterstock", "displayName": "Shutterstock API Explorer", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated Shutterstock API Explorer SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/shutterstock/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/shutterstock/skills/dotnet/dotnet-getting-started/SKILL.md index e1102ef7..9cac2b2d 100644 --- a/plugins/shutterstock/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/shutterstock/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Shutterstock API Explorer .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-shutterstock-api-explorer is the workflow that uses it." ---- - -# Getting started with the Shutterstock API Explorer .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Shutterstock API Explorer SDK fact. `dotnet-integrate-shutterstock-api-explorer` is the workflow that drives it — you collect the contracts for the work in scope into `shutterstock-api-explorer-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Shutterstock API Explorer | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/shutterstock-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `ShutterstockApiExplorer` (the `using` namespace) | -| Client class | `ShutterstockApiExplorerClient` | -| Options class | `ShutterstockApiExplorerClientOptions` | -| Auth | HTTP **Basic** — set `Basic`
**OAuth 2.0** authorization code — set `CustomerAccessCode` | -| Environments | `ServerEnvironment.Production` *(default)* → `Default` `https://api.shutterstock.com`, `AuthServer` `https://accounts.shutterstock.com/oauth`
`ServerEnvironment.Environment2` → `Default` `https://api-sandbox.shutterstock.com`, `AuthServer` `https://accounts.shutterstock.com/oauth` | -| Log environment variable | `SHUTTERSTOCKAPIEXPLORERCLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-shutterstock-api-explorer` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using ShutterstockApiExplorer.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `ShutterstockApiExplorerClient` | `ShutterstockApiExplorer` | -| `ShutterstockApiExplorerClientOptions` | `ShutterstockApiExplorer` | -| `ServerEnvironment` | `ShutterstockApiExplorer.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Shutterstock API Explorer, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/ShutterstockApiExplorer/Models/`, `src/ShutterstockApiExplorer/Errors/` and `src/ShutterstockApiExplorer/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/ShutterstockApiExplorer/Models/`, enums `src/ShutterstockApiExplorer/Models/Enums/`, typed errors `src/ShutterstockApiExplorer/Errors/`, request records `src/ShutterstockApiExplorer/Requests/`, or `src/ShutterstockApiExplorer/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `shutterstock-api-explorer-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/shutterstock-api-explorer-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/shutterstock-api-explorer-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/shutterstock-api-explorer-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/shutterstock-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\shutterstock-api-explorer-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/shutterstock-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `shutterstock-api-explorer-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/ShutterstockApiExplorer/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/ShutterstockApiExplorer/Models/` (+ `src/ShutterstockApiExplorer/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/ShutterstockApiExplorer/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/ShutterstockApiExplorer/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/ShutterstockApiExplorer/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/ShutterstockApiExplorer/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/ShutterstockApiExplorer/Servers/`, `src/ShutterstockApiExplorer/ShutterstockApiExplorerClient.cs`, `src/ShutterstockApiExplorer/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Shutterstock API Explorer may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `SHUTTERSTOCKAPIEXPLORERCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/ShutterstockApiExplorer/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-shutterstock-api-explorer`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new ShutterstockApiExplorerClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `ShutterstockApiExplorerClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Shutterstock API Explorer .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-shutterstock-api-explorer is the workflow that uses it." +--- + +# Getting started with the Shutterstock API Explorer .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Shutterstock API Explorer SDK fact. `dotnet-integrate-shutterstock-api-explorer` is the workflow that drives it — you collect the contracts for the work in scope into `shutterstock-api-explorer-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Shutterstock API Explorer | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/shutterstock-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `ShutterstockApiExplorer` (the `using` namespace) | +| Client class | `ShutterstockApiExplorerClient` | +| Options class | `ShutterstockApiExplorerClientOptions` | +| Auth | HTTP **Basic** — set `Basic`
**OAuth 2.0** authorization code — set `CustomerAccessCode` | +| Environments | `ServerEnvironment.Production` *(default)* → `Default` `https://api.shutterstock.com`, `AuthServer` `https://accounts.shutterstock.com/oauth`
`ServerEnvironment.Environment2` → `Default` `https://api-sandbox.shutterstock.com`, `AuthServer` `https://accounts.shutterstock.com/oauth` | +| Log environment variable | `SHUTTERSTOCKAPIEXPLORERCLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-shutterstock-api-explorer` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using ShutterstockApiExplorer.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `ShutterstockApiExplorerClient` | `ShutterstockApiExplorer` | +| `ShutterstockApiExplorerClientOptions` | `ShutterstockApiExplorer` | +| `ServerEnvironment` | `ShutterstockApiExplorer.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Shutterstock API Explorer, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/ShutterstockApiExplorer/Models/`, `src/ShutterstockApiExplorer/Errors/` and `src/ShutterstockApiExplorer/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/ShutterstockApiExplorer/Models/`, enums `src/ShutterstockApiExplorer/Models/Enums/`, typed errors `src/ShutterstockApiExplorer/Errors/`, request records `src/ShutterstockApiExplorer/Requests/`, or `src/ShutterstockApiExplorer/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `shutterstock-api-explorer-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/shutterstock-api-explorer-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/shutterstock-api-explorer-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/shutterstock-api-explorer-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/shutterstock-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\shutterstock-api-explorer-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/shutterstock-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `shutterstock-api-explorer-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/ShutterstockApiExplorer/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/ShutterstockApiExplorer/Models/` (+ `src/ShutterstockApiExplorer/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/ShutterstockApiExplorer/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/ShutterstockApiExplorer/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/ShutterstockApiExplorer/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/ShutterstockApiExplorer/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/ShutterstockApiExplorer/Servers/`, `src/ShutterstockApiExplorer/ShutterstockApiExplorerClient.cs`, `src/ShutterstockApiExplorer/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Shutterstock API Explorer may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `SHUTTERSTOCKAPIEXPLORERCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/ShutterstockApiExplorer/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-shutterstock-api-explorer`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new ShutterstockApiExplorerClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `ShutterstockApiExplorerClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/shutterstock/skills/python/python-getting-started/SKILL.md b/plugins/shutterstock/skills/python/python-getting-started/SKILL.md index d43c95d2..df93eb69 100644 --- a/plugins/shutterstock/skills/python/python-getting-started/SKILL.md +++ b/plugins/shutterstock/skills/python/python-getting-started/SKILL.md @@ -1,251 +1,251 @@ ---- -name: "python-getting-started" -description: "Shutterstock API Explorer Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Shutterstock API Explorer Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Shutterstock API Explorer Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Shutterstock API Explorer Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-shutterstock-api-explorer` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `shutterstock_api_explorer/` and `pyproject.toml` of the generated package at version `1.2.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Shutterstock API Explorer | -| Distribution name (what you install) | `shutterstock-api-explorer` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `shutterstock_api_explorer` — not the string you install | -| Source repository | https://github.com/context-plugins/shutterstock-python-sdk | -| Source branch | `main` | -| Version | `1.2.0` | -| Sync client class | `ShutterstockApiExplorerClient` (alias `Client`) | -| Async client class | `AsyncShutterstockApiExplorerClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `environment` · `timeout` (default `30.0`) · `server_config` · `retry_options` · `basic` · `customer_access_code` · `customer_access_code_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | HTTP **Basic** — set `basic` · **OAuth 2.0** authorization code — set `customer_access_code` | -| Environments | 2 environments (default `"production"`) × 2 named servers, through `server_config` | -| Base-URL config | `ServerConfig` (`shutterstock_api_explorer/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 109 operations across 12 controllers · 155 models · 8 unions · 66 enums · 100 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "shutterstock-api-explorer @ git+https://github.com/context-plugins/shutterstock-python-sdk.git@main" -python -c "import shutterstock_api_explorer, pathlib; print(pathlib.Path(shutterstock_api_explorer.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from shutterstock_api_explorer import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`shutterstock_api_explorer/__init__.py` exports exactly 8 names beside the `shutterstock_api_explorer.models` subpackage it re-exports: - -```python -from shutterstock_api_explorer import ( - AsyncClient, - AsyncShutterstockApiExplorerClient, - Client, - Environment, - ServerConfig, - ServerConfigDict, - ServerConfigOrDict, - ShutterstockApiExplorerClient, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `shutterstock_api_explorer.models` | -| Enums (and their open `…OrStr` aliases) | `shutterstock_api_explorer.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `BasicAuthCredentials` · `AuthorizationCodeCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `shutterstock_api_explorer.core` | -| Per-operation error *unions* | `shutterstock_api_explorer.errors` (`AddImageCollectionItemsErrorBody`, …) | - -`shutterstock_api_explorer.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments and servers - -The API declares 2 named servers across 2 environments, so the constructor takes `environment` first, then `timeout`, then `server_config: ServerConfigOrDict | None = None`. The environment selects which set of base URLs the config resolves against: - -| `environment=` | Hosting | -| --- | --- | -| `"production"` *(default)* | Live server | -| `"environment2"` | Sandbox server | - -Each of the 2 servers resolves independently, so a call's host is the pair (server, environment): - -| Server field | `"production"` base URL | `"environment2"` base URL | -| --- | --- | --- | -| `default` | `https://api.shutterstock.com` | `https://api-sandbox.shutterstock.com` | -| `auth_server` | `https://accounts.shutterstock.com/oauth` | `https://accounts.shutterstock.com/oauth` | - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `environment` gives you **`"production"`**, silently. -- `server_config` overrides individual server URLs within the selected environment. -- The token endpoint (`/v2/oauth/access_token`) resolves against the `default` server rather than a client-wide base URL, so `server_config` is what moves token traffic, and it follows `environment` with that server. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (two schemes) - -HTTP Basic authentication, exposed as the client's `basic=` keyword taking `BasicAuthCredentials` **or a plain dict**. - -```python -from shutterstock_api_explorer import Client -from shutterstock_api_explorer.core import BasicAuthCredentials - -client = Client(basic=BasicAuthCredentials(username="…", password="…")) -client = Client(basic={"username": "…", "password": "…"}) # equivalent -``` - -OAuth 2.0 authorization code, exposed as the client's `customer_access_code=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/v2/oauth/access_token` on the `default` server. - -```python -from shutterstock_api_explorer import Client -from shutterstock_api_explorer.core import AuthorizationCodeCredentials - -def prompt(url: str) -> str: - return input(f"Open {url}, then paste the code: ") - -client = Client(customer_access_code=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) -client = Client(customer_access_code={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.audio_api` | `AudioApi` / `AsyncAudioApi` | 17 | `add_track_collection_items` · `create_track_collection` · `delete_track_collection` · `delete_track_collection_items` · `download_tracks` · `get_track` · … | -| `client.catalog` | `Catalog` / `AsyncCatalog` | 7 | `add_to_collection` · `create_collection` · `delete_collection` · `delete_from_collection` · `get_collections` · `search_catalog` · … | -| `client.computer_vision` | `ComputerVision` / `AsyncComputerVision` | 4 | `get_keywords` · `get_similar_images` · `get_similar_videos` · `upload_image` | -| `client.contributors` | `Contributors` / `AsyncContributors` | 5 | `get_contributor` · `get_contributor_collection_items` · `get_contributor_collections` · `get_contributor_collections_list` · `get_contributor_list` | -| `client.editorial_images` | `EditorialImages` / `AsyncEditorialImages` | 18 | `get_editorial_categories` · `get_editorial_image` · `get_editorial_image2` · `get_editorial_image_license_list` · `get_editorial_image_livefeed` · `get_editorial_image_livefeed_items` · … | -| `client.editorial_video` | `EditorialVideo` / `AsyncEditorialVideo` | 6 | `get_editorial_video` · `get_editorial_video_license_list` · `license_editorial_video` · `list_editorial_video_categories` · `list_editorial_videos` · `search_editorial_videos` | -| `client.images` | `Images` / `AsyncImages` | 21 | `add_image_collection_items` · `bulk_search_images` · `create_image_collection` · `delete_image_collection` · `delete_image_collection_items` · `download_image` · … | -| `client.oauth` | `Oauth` / `AsyncOauth` | 2 | `authorize` · `create_access_token` | -| `client.sound_effects` | `SoundEffects` / `AsyncSoundEffects` | 6 | `download_sfx` · `get_sfx_details` · `get_sfx_license_list` · `get_sfx_list_details` · `licenses_sfx` · `search_sfx` | -| `client.test` | `Test` / `AsyncTest` | 2 | `echo` · `validate` | -| `client.users` | `Users` / `AsyncUsers` | 3 | `get_access_token` · `get_user` · `get_user_subscription_list` | -| `client.videos` | `Videos` / `AsyncVideos` | 18 | `add_video_collection_items` · `create_video_collection` · `delete_video_collection` · `delete_video_collection_items` · `download_videos` · `find_similar_videos` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `shutterstock_api_explorer/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `shutterstock_api_explorer/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/shutterstock-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `shutterstock_api_explorer/models/access_token_details.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `shutterstock_api_explorer/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import shutterstock_api_explorer, pathlib; print(pathlib.Path(shutterstock_api_explorer.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/shutterstock_api_explorer` on Windows, `.venv/lib/python3.*/site-packages/shutterstock_api_explorer` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `basic=` and `customer_access_code=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **14 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 14 operations that return `None`** — `audio_api.add_track_collection_items` · `audio_api.delete_track_collection` · `audio_api.delete_track_collection_items` · `audio_api.rename_track_collection` · `catalog.delete_collection` · `images.add_image_collection_items` · `images.delete_image_collection` · `images.delete_image_collection_items` · `images.rename_image_collection` · `oauth.authorize` · `videos.add_video_collection_items` · `videos.delete_video_collection` · `videos.delete_video_collection_items` · `videos.rename_video_collection`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 47 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"`. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Shutterstock API Explorer Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Shutterstock API Explorer Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Shutterstock API Explorer Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Shutterstock API Explorer Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-shutterstock-api-explorer` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `shutterstock_api_explorer/` and `pyproject.toml` of the generated package at version `1.2.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Shutterstock API Explorer | +| Distribution name (what you install) | `shutterstock-api-explorer` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `shutterstock_api_explorer` — not the string you install | +| Source repository | https://github.com/context-plugins/shutterstock-python-sdk | +| Source branch | `main` | +| Version | `1.2.0` | +| Sync client class | `ShutterstockApiExplorerClient` (alias `Client`) | +| Async client class | `AsyncShutterstockApiExplorerClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `environment` · `timeout` (default `30.0`) · `server_config` · `retry_options` · `basic` · `customer_access_code` · `customer_access_code_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | HTTP **Basic** — set `basic` · **OAuth 2.0** authorization code — set `customer_access_code` | +| Environments | 2 environments (default `"production"`) × 2 named servers, through `server_config` | +| Base-URL config | `ServerConfig` (`shutterstock_api_explorer/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 109 operations across 12 controllers · 155 models · 8 unions · 66 enums · 100 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "shutterstock-api-explorer @ git+https://github.com/context-plugins/shutterstock-python-sdk.git@main" +python -c "import shutterstock_api_explorer, pathlib; print(pathlib.Path(shutterstock_api_explorer.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from shutterstock_api_explorer import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`shutterstock_api_explorer/__init__.py` exports exactly 8 names beside the `shutterstock_api_explorer.models` subpackage it re-exports: + +```python +from shutterstock_api_explorer import ( + AsyncClient, + AsyncShutterstockApiExplorerClient, + Client, + Environment, + ServerConfig, + ServerConfigDict, + ServerConfigOrDict, + ShutterstockApiExplorerClient, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `shutterstock_api_explorer.models` | +| Enums (and their open `…OrStr` aliases) | `shutterstock_api_explorer.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `BasicAuthCredentials` · `AuthorizationCodeCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `shutterstock_api_explorer.core` | +| Per-operation error *unions* | `shutterstock_api_explorer.errors` (`AddImageCollectionItemsErrorBody`, …) | + +`shutterstock_api_explorer.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments and servers + +The API declares 2 named servers across 2 environments, so the constructor takes `environment` first, then `timeout`, then `server_config: ServerConfigOrDict | None = None`. The environment selects which set of base URLs the config resolves against: + +| `environment=` | Hosting | +| --- | --- | +| `"production"` *(default)* | Live server | +| `"environment2"` | Sandbox server | + +Each of the 2 servers resolves independently, so a call's host is the pair (server, environment): + +| Server field | `"production"` base URL | `"environment2"` base URL | +| --- | --- | --- | +| `default` | `https://api.shutterstock.com` | `https://api-sandbox.shutterstock.com` | +| `auth_server` | `https://accounts.shutterstock.com/oauth` | `https://accounts.shutterstock.com/oauth` | + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `environment` gives you **`"production"`**, silently. +- `server_config` overrides individual server URLs within the selected environment. +- The token endpoint (`/v2/oauth/access_token`) resolves against the `default` server rather than a client-wide base URL, so `server_config` is what moves token traffic, and it follows `environment` with that server. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (two schemes) + +HTTP Basic authentication, exposed as the client's `basic=` keyword taking `BasicAuthCredentials` **or a plain dict**. + +```python +from shutterstock_api_explorer import Client +from shutterstock_api_explorer.core import BasicAuthCredentials + +client = Client(basic=BasicAuthCredentials(username="…", password="…")) +client = Client(basic={"username": "…", "password": "…"}) # equivalent +``` + +OAuth 2.0 authorization code, exposed as the client's `customer_access_code=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/v2/oauth/access_token` on the `default` server. + +```python +from shutterstock_api_explorer import Client +from shutterstock_api_explorer.core import AuthorizationCodeCredentials + +def prompt(url: str) -> str: + return input(f"Open {url}, then paste the code: ") + +client = Client(customer_access_code=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) +client = Client(customer_access_code={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.audio_api` | `AudioApi` / `AsyncAudioApi` | 17 | `add_track_collection_items` · `create_track_collection` · `delete_track_collection` · `delete_track_collection_items` · `download_tracks` · `get_track` · … | +| `client.catalog` | `Catalog` / `AsyncCatalog` | 7 | `add_to_collection` · `create_collection` · `delete_collection` · `delete_from_collection` · `get_collections` · `search_catalog` · … | +| `client.computer_vision` | `ComputerVision` / `AsyncComputerVision` | 4 | `get_keywords` · `get_similar_images` · `get_similar_videos` · `upload_image` | +| `client.contributors` | `Contributors` / `AsyncContributors` | 5 | `get_contributor` · `get_contributor_collection_items` · `get_contributor_collections` · `get_contributor_collections_list` · `get_contributor_list` | +| `client.editorial_images` | `EditorialImages` / `AsyncEditorialImages` | 18 | `get_editorial_categories` · `get_editorial_image` · `get_editorial_image2` · `get_editorial_image_license_list` · `get_editorial_image_livefeed` · `get_editorial_image_livefeed_items` · … | +| `client.editorial_video` | `EditorialVideo` / `AsyncEditorialVideo` | 6 | `get_editorial_video` · `get_editorial_video_license_list` · `license_editorial_video` · `list_editorial_video_categories` · `list_editorial_videos` · `search_editorial_videos` | +| `client.images` | `Images` / `AsyncImages` | 21 | `add_image_collection_items` · `bulk_search_images` · `create_image_collection` · `delete_image_collection` · `delete_image_collection_items` · `download_image` · … | +| `client.oauth` | `Oauth` / `AsyncOauth` | 2 | `authorize` · `create_access_token` | +| `client.sound_effects` | `SoundEffects` / `AsyncSoundEffects` | 6 | `download_sfx` · `get_sfx_details` · `get_sfx_license_list` · `get_sfx_list_details` · `licenses_sfx` · `search_sfx` | +| `client.test` | `Test` / `AsyncTest` | 2 | `echo` · `validate` | +| `client.users` | `Users` / `AsyncUsers` | 3 | `get_access_token` · `get_user` · `get_user_subscription_list` | +| `client.videos` | `Videos` / `AsyncVideos` | 18 | `add_video_collection_items` · `create_video_collection` · `delete_video_collection` · `delete_video_collection_items` · `download_videos` · `find_similar_videos` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `shutterstock_api_explorer/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `shutterstock_api_explorer/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/shutterstock-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `shutterstock_api_explorer/models/access_token_details.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `shutterstock_api_explorer/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import shutterstock_api_explorer, pathlib; print(pathlib.Path(shutterstock_api_explorer.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/shutterstock_api_explorer` on Windows, `.venv/lib/python3.*/site-packages/shutterstock_api_explorer` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `basic=` and `customer_access_code=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **14 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 14 operations that return `None`** — `audio_api.add_track_collection_items` · `audio_api.delete_track_collection` · `audio_api.delete_track_collection_items` · `audio_api.rename_track_collection` · `catalog.delete_collection` · `images.add_image_collection_items` · `images.delete_image_collection` · `images.delete_image_collection_items` · `images.rename_image_collection` · `oauth.authorize` · `videos.add_video_collection_items` · `videos.delete_video_collection` · `videos.delete_video_collection_items` · `videos.rename_video_collection`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 47 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"`. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/slack/.claude-plugin/plugin.json b/plugins/slack/.claude-plugin/plugin.json index b7b3bf09..1a6d6d47 100644 --- a/plugins/slack/.claude-plugin/plugin.json +++ b/plugins/slack/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "slack", "displayName": "Slack Web API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Slack Web API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/slack/.cursor-plugin/plugin.json b/plugins/slack/.cursor-plugin/plugin.json index b7b3bf09..1a6d6d47 100644 --- a/plugins/slack/.cursor-plugin/plugin.json +++ b/plugins/slack/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "slack", "displayName": "Slack Web API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Slack Web API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/slack/README.md b/plugins/slack/README.md index 0d3e2ccc..f37c2a4e 100644 --- a/plugins/slack/README.md +++ b/plugins/slack/README.md @@ -1,13 +1,13 @@ -# Slack Web API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Slack Web API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-slack`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-slack`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-slack`, `typescript-models`, `typescript-testing` | +# Slack Web API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Slack Web API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-slack`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-slack`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-slack`, `typescript-models`, `typescript-testing` | diff --git a/plugins/slack/plugin.json b/plugins/slack/plugin.json index b7b3bf09..1a6d6d47 100644 --- a/plugins/slack/plugin.json +++ b/plugins/slack/plugin.json @@ -1,7 +1,7 @@ { "name": "slack", "displayName": "Slack Web API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Slack Web API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/slack/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/slack/skills/dotnet/dotnet-getting-started/SKILL.md index 2ef2e605..2a41611e 100644 --- a/plugins/slack/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/slack/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Slack Web API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-slack-web-api is the workflow that uses it." ---- - -# Getting started with the Slack Web API .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Slack Web API SDK fact. `dotnet-integrate-slack-web-api` is the workflow that drives it — you collect the contracts for the work in scope into `slack-web-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Slack Web API | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/slack-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `SlackWebApi` (the `using` namespace) | -| Client class | `SlackWebApiClient` | -| Options class | `SlackWebApiClientOptions` | -| Auth | **OAuth 2.0** authorization code — set `SlackAuth` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://slack.com/api`
`ServerEnvironment.Environment2` → `https://slack.com/oauth` | -| Log environment variable | `SLACKWEBAPICLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-slack-web-api` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using SlackWebApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `SlackWebApiClient` | `SlackWebApi` | -| `SlackWebApiClientOptions` | `SlackWebApi` | -| `ServerEnvironment` | `SlackWebApi.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Slack Web API, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/SlackWebApi/Models/`, `src/SlackWebApi/Errors/` and `src/SlackWebApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/SlackWebApi/Models/`, enums `src/SlackWebApi/Models/Enums/`, typed errors `src/SlackWebApi/Errors/`, request records `src/SlackWebApi/Requests/`, or `src/SlackWebApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `slack-web-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/slack-web-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/slack-web-api-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/slack-web-api-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/slack-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\slack-web-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/slack-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `slack-web-api-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/SlackWebApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/SlackWebApi/Models/` (+ `src/SlackWebApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/SlackWebApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/SlackWebApi/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/SlackWebApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/SlackWebApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/SlackWebApi/Servers/`, `src/SlackWebApi/SlackWebApiClient.cs`, `src/SlackWebApi/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Slack Web API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `SLACKWEBAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/SlackWebApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-slack-web-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new SlackWebApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `SlackWebApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Slack Web API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-slack-web-api is the workflow that uses it." +--- + +# Getting started with the Slack Web API .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Slack Web API SDK fact. `dotnet-integrate-slack-web-api` is the workflow that drives it — you collect the contracts for the work in scope into `slack-web-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Slack Web API | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/slack-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `SlackWebApi` (the `using` namespace) | +| Client class | `SlackWebApiClient` | +| Options class | `SlackWebApiClientOptions` | +| Auth | **OAuth 2.0** authorization code — set `SlackAuth` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://slack.com/api`
`ServerEnvironment.Environment2` → `https://slack.com/oauth` | +| Log environment variable | `SLACKWEBAPICLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-slack-web-api` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using SlackWebApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `SlackWebApiClient` | `SlackWebApi` | +| `SlackWebApiClientOptions` | `SlackWebApi` | +| `ServerEnvironment` | `SlackWebApi.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Slack Web API, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/SlackWebApi/Models/`, `src/SlackWebApi/Errors/` and `src/SlackWebApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/SlackWebApi/Models/`, enums `src/SlackWebApi/Models/Enums/`, typed errors `src/SlackWebApi/Errors/`, request records `src/SlackWebApi/Requests/`, or `src/SlackWebApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `slack-web-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/slack-web-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/slack-web-api-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/slack-web-api-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/slack-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\slack-web-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/slack-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `slack-web-api-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/SlackWebApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/SlackWebApi/Models/` (+ `src/SlackWebApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/SlackWebApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/SlackWebApi/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/SlackWebApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/SlackWebApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/SlackWebApi/Servers/`, `src/SlackWebApi/SlackWebApiClient.cs`, `src/SlackWebApi/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Slack Web API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `SLACKWEBAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/SlackWebApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-slack-web-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new SlackWebApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `SlackWebApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/slack/skills/python/python-getting-started/SKILL.md b/plugins/slack/skills/python/python-getting-started/SKILL.md index be3c5f92..779f3909 100644 --- a/plugins/slack/skills/python/python-getting-started/SKILL.md +++ b/plugins/slack/skills/python/python-getting-started/SKILL.md @@ -1,275 +1,275 @@ ---- -name: "python-getting-started" -description: "Slack Web API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Slack Web API Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Slack Web API Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Slack Web API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-slack-web-api` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `slack_web_api/` and `pyproject.toml` of the generated package at version `1.7.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Slack Web API | -| Distribution name (what you install) | `slack-web-api` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `slack_web_api` — not the string you install | -| Source repository | https://github.com/context-plugins/slack-python-sdk | -| Source branch | `main` | -| Version | `1.7.0` | -| Sync client class | `SlackWebApiClient` (alias `Client`) | -| Async client class | `AsyncSlackWebApiClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `slack_auth` · `slack_auth_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **OAuth 2.0** authorization code — set `slack_auth` | -| Environments | 2 environments selected by `environment` (default `"production"`), overridable with `base_url` | -| Base-URL config | `ServerConfig` (`slack_web_api/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 253 operations across 56 controllers · 357 models · 0 unions · 89 enums · 0 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "slack-web-api @ git+https://github.com/context-plugins/slack-python-sdk.git@main" -python -c "import slack_web_api, pathlib; print(pathlib.Path(slack_web_api.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from slack_web_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`slack_web_api/__init__.py` exports exactly 6 names beside the `slack_web_api.models` subpackage it re-exports: - -```python -from slack_web_api import ( - AsyncClient, - AsyncSlackWebApiClient, - Client, - Environment, - ServerConfig, - SlackWebApiClient, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `slack_web_api.models` | -| Enums (and their open `…OrStr` aliases) | `slack_web_api.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `AuthorizationCodeCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `slack_web_api.core` | -| Per-operation error *unions* | `slack_web_api.errors` — *this SDK declares none* | - -`slack_web_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments - -The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: - -| `environment=` | Base URL | Hosting | -| --- | --- | --- | -| `"production"` *(default)* | `https://slack.com/api` | — | -| `"environment2"` | `https://slack.com/oauth` | — | - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `environment` gives you **`"production"`**, silently — it is whatever the description listed first, not necessarily production. -- `base_url` overrides the environment's URL entirely when both are passed. -- The token endpoint is derived from the same base URL (`/oauth.access`), so it always follows the environment — you never configure it separately. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (one scheme) - -OAuth 2.0 authorization code, exposed as the client's `slack_auth=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/oauth.access`. - -```python -from slack_web_api import Client -from slack_web_api.core import AuthorizationCodeCredentials - -def prompt(url: str) -> str: - return input(f"Open {url}, then paste the code: ") - -client = Client(slack_auth=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) -client = Client(slack_auth={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.admin` | `Admin` / `AsyncAdmin` | 56 | `admin_apps_approve` · `admin_apps_approved_list` · `admin_apps_requests_list` · `admin_apps_restrict` · `admin_apps_restricted_list` · `admin_conversations_archive` · … | -| `client.admin_apps` | `AdminApps` / `AsyncAdminApps` | 2 | `admin_apps_approve` · `admin_apps_restrict` | -| `client.admin_apps_approved` | `AdminAppsApproved` / `AsyncAdminAppsApproved` | 1 | `admin_apps_approved_list` | -| `client.admin_apps_requests` | `AdminAppsRequests` / `AsyncAdminAppsRequests` | 1 | `admin_apps_requests_list` | -| `client.admin_apps_restricted` | `AdminAppsRestricted` / `AsyncAdminAppsRestricted` | 1 | `admin_apps_restricted_list` | -| `client.admin_conversations` | `AdminConversations` / `AsyncAdminConversations` | 13 | `admin_conversations_archive` · `admin_conversations_convert_to_private` · `admin_conversations_create` · `admin_conversations_delete` · `admin_conversations_disconnect_shared` · `admin_conversations_get_conversation_prefs` · … | -| `client.admin_conversations_ekm` | `AdminConversationsEkm` / `AsyncAdminConversationsEkm` | 1 | `admin_conversations_ekm_list_original_connected_channel_info` | -| `client.admin_conversations_restrict_access` | `AdminConversationsRestrictAccess` / `AsyncAdminConversationsRestrictAccess` | 3 | `admin_conversations_restrict_access_add_group` · `admin_conversations_restrict_access_list_groups` · `admin_conversations_restrict_access_remove_group` | -| `client.admin_emoji` | `AdminEmoji` / `AsyncAdminEmoji` | 5 | `admin_emoji_add` · `admin_emoji_add_alias` · `admin_emoji_list` · `admin_emoji_remove` · `admin_emoji_rename` | -| `client.admin_invite_requests` | `AdminInviteRequests` / `AsyncAdminInviteRequests` | 3 | `admin_invite_requests_approve` · `admin_invite_requests_deny` · `admin_invite_requests_list` | -| `client.admin_invite_requests_approved` | `AdminInviteRequestsApproved` / `AsyncAdminInviteRequestsApproved` | 1 | `admin_invite_requests_approved_list` | -| `client.admin_invite_requests_denied` | `AdminInviteRequestsDenied` / `AsyncAdminInviteRequestsDenied` | 1 | `admin_invite_requests_denied_list` | -| `client.admin_teams` | `AdminTeams` / `AsyncAdminTeams` | 2 | `admin_teams_create` · `admin_teams_list` | -| `client.admin_teams_admins` | `AdminTeamsAdmins` / `AsyncAdminTeamsAdmins` | 1 | `admin_teams_admins_list` | -| `client.admin_teams_owners` | `AdminTeamsOwners` / `AsyncAdminTeamsOwners` | 1 | `admin_teams_owners_list` | -| `client.admin_teams_settings` | `AdminTeamsSettings` / `AsyncAdminTeamsSettings` | 6 | `admin_teams_settings_info` · `admin_teams_settings_set_default_channels` · `admin_teams_settings_set_description` · `admin_teams_settings_set_discoverability` · `admin_teams_settings_set_icon` · `admin_teams_settings_set_name` | -| `client.admin_usergroups` | `AdminUsergroups` / `AsyncAdminUsergroups` | 4 | `admin_usergroups_add_channels` · `admin_usergroups_add_teams` · `admin_usergroups_list_channels` · `admin_usergroups_remove_channels` | -| `client.admin_users` | `AdminUsers` / `AsyncAdminUsers` | 8 | `admin_users_assign` · `admin_users_invite` · `admin_users_list` · `admin_users_remove` · `admin_users_set_admin` · `admin_users_set_expiration` · … | -| `client.admin_users_session` | `AdminUsersSession` / `AsyncAdminUsersSession` | 2 | `admin_users_session_invalidate` · `admin_users_session_reset` | -| `client.api2` | `Api2` / `AsyncApi2` | 1 | `api_test` | -| `client.apps` | `Apps` / `AsyncApps` | 8 | `apps_event_authorizations_list` · `apps_permissions_info` · `apps_permissions_request` · `apps_permissions_resources_list` · `apps_permissions_scopes_list` · `apps_permissions_users_list` · … | -| `client.apps_event_authorizations` | `AppsEventAuthorizations` / `AsyncAppsEventAuthorizations` | 1 | `apps_event_authorizations_list` | -| `client.apps_permissions` | `AppsPermissions` / `AsyncAppsPermissions` | 2 | `apps_permissions_info` · `apps_permissions_request` | -| `client.apps_permissions_resources` | `AppsPermissionsResources` / `AsyncAppsPermissionsResources` | 1 | `apps_permissions_resources_list` | -| `client.apps_permissions_scopes` | `AppsPermissionsScopes` / `AsyncAppsPermissionsScopes` | 1 | `apps_permissions_scopes_list` | -| `client.apps_permissions_users` | `AppsPermissionsUsers` / `AsyncAppsPermissionsUsers` | 2 | `apps_permissions_users_list` · `apps_permissions_users_request` | -| `client.auth_api` | `AuthApi` / `AsyncAuthApi` | 2 | `auth_revoke` · `auth_test` | -| `client.bots` | `Bots` / `AsyncBots` | 1 | `bots_info` | -| `client.calls` | `Calls` / `AsyncCalls` | 6 | `calls_add` · `calls_end` · `calls_info` · `calls_participants_add` · `calls_participants_remove` · `calls_update` | -| `client.calls_participants` | `CallsParticipants` / `AsyncCallsParticipants` | 2 | `calls_participants_add` · `calls_participants_remove` | -| `client.chat` | `Chat` / `AsyncChat` | 10 | `chat_delete` · `chat_delete_scheduled_message` · `chat_get_permalink` · `chat_me_message` · `chat_post_ephemeral` · `chat_post_message` · … | -| `client.chat_scheduled_messages` | `ChatScheduledMessages` / `AsyncChatScheduledMessages` | 1 | `chat_scheduled_messages_list` | -| `client.conversations` | `Conversations` / `AsyncConversations` | 18 | `conversations_archive` · `conversations_close` · `conversations_create` · `conversations_history` · `conversations_info` · `conversations_invite` · … | -| `client.dialog` | `Dialog` / `AsyncDialog` | 1 | `dialog_open` | -| `client.dnd` | `Dnd` / `AsyncDnd` | 5 | `dnd_end_dnd` · `dnd_end_snooze` · `dnd_info` · `dnd_set_snooze` · `dnd_team_info` | -| `client.emoji` | `Emoji` / `AsyncEmoji` | 1 | `emoji_list` | -| `client.files` | `Files` / `AsyncFiles` | 13 | `files_comments_delete` · `files_delete` · `files_info` · `files_list` · `files_remote_add` · `files_remote_info` · … | -| `client.files_comments` | `FilesComments` / `AsyncFilesComments` | 1 | `files_comments_delete` | -| `client.files_remote` | `FilesRemote` / `AsyncFilesRemote` | 6 | `files_remote_add` · `files_remote_info` · `files_remote_list` · `files_remote_remove` · `files_remote_share` · `files_remote_update` | -| `client.migration` | `Migration` / `AsyncMigration` | 1 | `migration_exchange` | -| `client.oauth` | `Oauth` / `AsyncOauth` | 3 | `oauth_access` · `oauth_token` · `oauth_v2_access` | -| `client.oauth_v2` | `OauthV2` / `AsyncOauthV2` | 1 | `oauth_v2_access` | -| `client.pins` | `Pins` / `AsyncPins` | 3 | `pins_add` · `pins_list` · `pins_remove` | -| `client.reactions` | `Reactions` / `AsyncReactions` | 4 | `reactions_add` · `reactions_get` · `reactions_list` · `reactions_remove` | -| `client.reminders` | `Reminders` / `AsyncReminders` | 5 | `reminders_add` · `reminders_complete` · `reminders_delete` · `reminders_info` · `reminders_list` | -| `client.rtm` | `Rtm` / `AsyncRtm` | 1 | `rtm_connect` | -| `client.search` | `Search` / `AsyncSearch` | 1 | `search_messages` | -| `client.stars` | `Stars` / `AsyncStars` | 3 | `stars_add` · `stars_list` · `stars_remove` | -| `client.team_api` | `TeamApi` / `AsyncTeamApi` | 5 | `team_access_logs` · `team_billable_info` · `team_info` · `team_integration_logs` · `team_profile_get` | -| `client.team_profile` | `TeamProfile` / `AsyncTeamProfile` | 1 | `team_profile_get` | -| `client.usergroups` | `Usergroups` / `AsyncUsergroups` | 7 | `usergroups_create` · `usergroups_disable` · `usergroups_enable` · `usergroups_list` · `usergroups_update` · `usergroups_users_list` · … | -| `client.usergroups_users` | `UsergroupsUsers` / `AsyncUsergroupsUsers` | 2 | `usergroups_users_list` · `usergroups_users_update` | -| `client.users` | `Users` / `AsyncUsers` | 12 | `users_conversations` · `users_delete_photo` · `users_get_presence` · `users_identity` · `users_info` · `users_list` · … | -| `client.users_profile` | `UsersProfile` / `AsyncUsersProfile` | 2 | `users_profile_get` · `users_profile_set` | -| `client.views` | `Views` / `AsyncViews` | 4 | `views_open` · `views_publish` · `views_push` · `views_update` | -| `client.workflows` | `Workflows` / `AsyncWorkflows` | 3 | `workflows_step_completed` · `workflows_step_failed` · `workflows_update_step` | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `slack_web_api/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `slack_web_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/slack-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `slack_web_api/models/apimethodusers_get_presence.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `slack_web_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import slack_web_api, pathlib; print(pathlib.Path(slack_web_api.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/slack_web_api` on Windows, `.venv/lib/python3.*/site-packages/slack_web_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `slack_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 250 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"` of the 2 declared. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Slack Web API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Slack Web API Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Slack Web API Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Slack Web API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-slack-web-api` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `slack_web_api/` and `pyproject.toml` of the generated package at version `1.7.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Slack Web API | +| Distribution name (what you install) | `slack-web-api` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `slack_web_api` — not the string you install | +| Source repository | https://github.com/context-plugins/slack-python-sdk | +| Source branch | `main` | +| Version | `1.7.0` | +| Sync client class | `SlackWebApiClient` (alias `Client`) | +| Async client class | `AsyncSlackWebApiClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `slack_auth` · `slack_auth_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **OAuth 2.0** authorization code — set `slack_auth` | +| Environments | 2 environments selected by `environment` (default `"production"`), overridable with `base_url` | +| Base-URL config | `ServerConfig` (`slack_web_api/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 253 operations across 56 controllers · 357 models · 0 unions · 89 enums · 0 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "slack-web-api @ git+https://github.com/context-plugins/slack-python-sdk.git@main" +python -c "import slack_web_api, pathlib; print(pathlib.Path(slack_web_api.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from slack_web_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`slack_web_api/__init__.py` exports exactly 6 names beside the `slack_web_api.models` subpackage it re-exports: + +```python +from slack_web_api import ( + AsyncClient, + AsyncSlackWebApiClient, + Client, + Environment, + ServerConfig, + SlackWebApiClient, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `slack_web_api.models` | +| Enums (and their open `…OrStr` aliases) | `slack_web_api.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `AuthorizationCodeCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `slack_web_api.core` | +| Per-operation error *unions* | `slack_web_api.errors` — *this SDK declares none* | + +`slack_web_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments + +The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: + +| `environment=` | Base URL | Hosting | +| --- | --- | --- | +| `"production"` *(default)* | `https://slack.com/api` | — | +| `"environment2"` | `https://slack.com/oauth` | — | + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `environment` gives you **`"production"`**, silently — it is whatever the description listed first, not necessarily production. +- `base_url` overrides the environment's URL entirely when both are passed. +- The token endpoint is derived from the same base URL (`/oauth.access`), so it always follows the environment — you never configure it separately. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (one scheme) + +OAuth 2.0 authorization code, exposed as the client's `slack_auth=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/oauth.access`. + +```python +from slack_web_api import Client +from slack_web_api.core import AuthorizationCodeCredentials + +def prompt(url: str) -> str: + return input(f"Open {url}, then paste the code: ") + +client = Client(slack_auth=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) +client = Client(slack_auth={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.admin` | `Admin` / `AsyncAdmin` | 56 | `admin_apps_approve` · `admin_apps_approved_list` · `admin_apps_requests_list` · `admin_apps_restrict` · `admin_apps_restricted_list` · `admin_conversations_archive` · … | +| `client.admin_apps` | `AdminApps` / `AsyncAdminApps` | 2 | `admin_apps_approve` · `admin_apps_restrict` | +| `client.admin_apps_approved` | `AdminAppsApproved` / `AsyncAdminAppsApproved` | 1 | `admin_apps_approved_list` | +| `client.admin_apps_requests` | `AdminAppsRequests` / `AsyncAdminAppsRequests` | 1 | `admin_apps_requests_list` | +| `client.admin_apps_restricted` | `AdminAppsRestricted` / `AsyncAdminAppsRestricted` | 1 | `admin_apps_restricted_list` | +| `client.admin_conversations` | `AdminConversations` / `AsyncAdminConversations` | 13 | `admin_conversations_archive` · `admin_conversations_convert_to_private` · `admin_conversations_create` · `admin_conversations_delete` · `admin_conversations_disconnect_shared` · `admin_conversations_get_conversation_prefs` · … | +| `client.admin_conversations_ekm` | `AdminConversationsEkm` / `AsyncAdminConversationsEkm` | 1 | `admin_conversations_ekm_list_original_connected_channel_info` | +| `client.admin_conversations_restrict_access` | `AdminConversationsRestrictAccess` / `AsyncAdminConversationsRestrictAccess` | 3 | `admin_conversations_restrict_access_add_group` · `admin_conversations_restrict_access_list_groups` · `admin_conversations_restrict_access_remove_group` | +| `client.admin_emoji` | `AdminEmoji` / `AsyncAdminEmoji` | 5 | `admin_emoji_add` · `admin_emoji_add_alias` · `admin_emoji_list` · `admin_emoji_remove` · `admin_emoji_rename` | +| `client.admin_invite_requests` | `AdminInviteRequests` / `AsyncAdminInviteRequests` | 3 | `admin_invite_requests_approve` · `admin_invite_requests_deny` · `admin_invite_requests_list` | +| `client.admin_invite_requests_approved` | `AdminInviteRequestsApproved` / `AsyncAdminInviteRequestsApproved` | 1 | `admin_invite_requests_approved_list` | +| `client.admin_invite_requests_denied` | `AdminInviteRequestsDenied` / `AsyncAdminInviteRequestsDenied` | 1 | `admin_invite_requests_denied_list` | +| `client.admin_teams` | `AdminTeams` / `AsyncAdminTeams` | 2 | `admin_teams_create` · `admin_teams_list` | +| `client.admin_teams_admins` | `AdminTeamsAdmins` / `AsyncAdminTeamsAdmins` | 1 | `admin_teams_admins_list` | +| `client.admin_teams_owners` | `AdminTeamsOwners` / `AsyncAdminTeamsOwners` | 1 | `admin_teams_owners_list` | +| `client.admin_teams_settings` | `AdminTeamsSettings` / `AsyncAdminTeamsSettings` | 6 | `admin_teams_settings_info` · `admin_teams_settings_set_default_channels` · `admin_teams_settings_set_description` · `admin_teams_settings_set_discoverability` · `admin_teams_settings_set_icon` · `admin_teams_settings_set_name` | +| `client.admin_usergroups` | `AdminUsergroups` / `AsyncAdminUsergroups` | 4 | `admin_usergroups_add_channels` · `admin_usergroups_add_teams` · `admin_usergroups_list_channels` · `admin_usergroups_remove_channels` | +| `client.admin_users` | `AdminUsers` / `AsyncAdminUsers` | 8 | `admin_users_assign` · `admin_users_invite` · `admin_users_list` · `admin_users_remove` · `admin_users_set_admin` · `admin_users_set_expiration` · … | +| `client.admin_users_session` | `AdminUsersSession` / `AsyncAdminUsersSession` | 2 | `admin_users_session_invalidate` · `admin_users_session_reset` | +| `client.api2` | `Api2` / `AsyncApi2` | 1 | `api_test` | +| `client.apps` | `Apps` / `AsyncApps` | 8 | `apps_event_authorizations_list` · `apps_permissions_info` · `apps_permissions_request` · `apps_permissions_resources_list` · `apps_permissions_scopes_list` · `apps_permissions_users_list` · … | +| `client.apps_event_authorizations` | `AppsEventAuthorizations` / `AsyncAppsEventAuthorizations` | 1 | `apps_event_authorizations_list` | +| `client.apps_permissions` | `AppsPermissions` / `AsyncAppsPermissions` | 2 | `apps_permissions_info` · `apps_permissions_request` | +| `client.apps_permissions_resources` | `AppsPermissionsResources` / `AsyncAppsPermissionsResources` | 1 | `apps_permissions_resources_list` | +| `client.apps_permissions_scopes` | `AppsPermissionsScopes` / `AsyncAppsPermissionsScopes` | 1 | `apps_permissions_scopes_list` | +| `client.apps_permissions_users` | `AppsPermissionsUsers` / `AsyncAppsPermissionsUsers` | 2 | `apps_permissions_users_list` · `apps_permissions_users_request` | +| `client.auth_api` | `AuthApi` / `AsyncAuthApi` | 2 | `auth_revoke` · `auth_test` | +| `client.bots` | `Bots` / `AsyncBots` | 1 | `bots_info` | +| `client.calls` | `Calls` / `AsyncCalls` | 6 | `calls_add` · `calls_end` · `calls_info` · `calls_participants_add` · `calls_participants_remove` · `calls_update` | +| `client.calls_participants` | `CallsParticipants` / `AsyncCallsParticipants` | 2 | `calls_participants_add` · `calls_participants_remove` | +| `client.chat` | `Chat` / `AsyncChat` | 10 | `chat_delete` · `chat_delete_scheduled_message` · `chat_get_permalink` · `chat_me_message` · `chat_post_ephemeral` · `chat_post_message` · … | +| `client.chat_scheduled_messages` | `ChatScheduledMessages` / `AsyncChatScheduledMessages` | 1 | `chat_scheduled_messages_list` | +| `client.conversations` | `Conversations` / `AsyncConversations` | 18 | `conversations_archive` · `conversations_close` · `conversations_create` · `conversations_history` · `conversations_info` · `conversations_invite` · … | +| `client.dialog` | `Dialog` / `AsyncDialog` | 1 | `dialog_open` | +| `client.dnd` | `Dnd` / `AsyncDnd` | 5 | `dnd_end_dnd` · `dnd_end_snooze` · `dnd_info` · `dnd_set_snooze` · `dnd_team_info` | +| `client.emoji` | `Emoji` / `AsyncEmoji` | 1 | `emoji_list` | +| `client.files` | `Files` / `AsyncFiles` | 13 | `files_comments_delete` · `files_delete` · `files_info` · `files_list` · `files_remote_add` · `files_remote_info` · … | +| `client.files_comments` | `FilesComments` / `AsyncFilesComments` | 1 | `files_comments_delete` | +| `client.files_remote` | `FilesRemote` / `AsyncFilesRemote` | 6 | `files_remote_add` · `files_remote_info` · `files_remote_list` · `files_remote_remove` · `files_remote_share` · `files_remote_update` | +| `client.migration` | `Migration` / `AsyncMigration` | 1 | `migration_exchange` | +| `client.oauth` | `Oauth` / `AsyncOauth` | 3 | `oauth_access` · `oauth_token` · `oauth_v2_access` | +| `client.oauth_v2` | `OauthV2` / `AsyncOauthV2` | 1 | `oauth_v2_access` | +| `client.pins` | `Pins` / `AsyncPins` | 3 | `pins_add` · `pins_list` · `pins_remove` | +| `client.reactions` | `Reactions` / `AsyncReactions` | 4 | `reactions_add` · `reactions_get` · `reactions_list` · `reactions_remove` | +| `client.reminders` | `Reminders` / `AsyncReminders` | 5 | `reminders_add` · `reminders_complete` · `reminders_delete` · `reminders_info` · `reminders_list` | +| `client.rtm` | `Rtm` / `AsyncRtm` | 1 | `rtm_connect` | +| `client.search` | `Search` / `AsyncSearch` | 1 | `search_messages` | +| `client.stars` | `Stars` / `AsyncStars` | 3 | `stars_add` · `stars_list` · `stars_remove` | +| `client.team_api` | `TeamApi` / `AsyncTeamApi` | 5 | `team_access_logs` · `team_billable_info` · `team_info` · `team_integration_logs` · `team_profile_get` | +| `client.team_profile` | `TeamProfile` / `AsyncTeamProfile` | 1 | `team_profile_get` | +| `client.usergroups` | `Usergroups` / `AsyncUsergroups` | 7 | `usergroups_create` · `usergroups_disable` · `usergroups_enable` · `usergroups_list` · `usergroups_update` · `usergroups_users_list` · … | +| `client.usergroups_users` | `UsergroupsUsers` / `AsyncUsergroupsUsers` | 2 | `usergroups_users_list` · `usergroups_users_update` | +| `client.users` | `Users` / `AsyncUsers` | 12 | `users_conversations` · `users_delete_photo` · `users_get_presence` · `users_identity` · `users_info` · `users_list` · … | +| `client.users_profile` | `UsersProfile` / `AsyncUsersProfile` | 2 | `users_profile_get` · `users_profile_set` | +| `client.views` | `Views` / `AsyncViews` | 4 | `views_open` · `views_publish` · `views_push` · `views_update` | +| `client.workflows` | `Workflows` / `AsyncWorkflows` | 3 | `workflows_step_completed` · `workflows_step_failed` · `workflows_update_step` | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `slack_web_api/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `slack_web_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/slack-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `slack_web_api/models/apimethodusers_get_presence.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `slack_web_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import slack_web_api, pathlib; print(pathlib.Path(slack_web_api.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/slack_web_api` on Windows, `.venv/lib/python3.*/site-packages/slack_web_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `slack_auth=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **Every operation returns a payload** — no operation in this SDK returns `None`, so the `with_raw_response` peer is needed only where the status code itself matters. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 250 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"` of the 2 declared. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/sportsdata/.claude-plugin/plugin.json b/plugins/sportsdata/.claude-plugin/plugin.json index a9d6ec48..7ea47012 100644 --- a/plugins/sportsdata/.claude-plugin/plugin.json +++ b/plugins/sportsdata/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "sportsdata", "displayName": "SportsDataIO", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated SportsDataIO SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/sportsdata/.cursor-plugin/plugin.json b/plugins/sportsdata/.cursor-plugin/plugin.json index a9d6ec48..7ea47012 100644 --- a/plugins/sportsdata/.cursor-plugin/plugin.json +++ b/plugins/sportsdata/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "sportsdata", "displayName": "SportsDataIO", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated SportsDataIO SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/sportsdata/README.md b/plugins/sportsdata/README.md index 7fc712e7..5476c874 100644 --- a/plugins/sportsdata/README.md +++ b/plugins/sportsdata/README.md @@ -1,13 +1,13 @@ -# SportsDataIO SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **SportsDataIO SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-sportsdata`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-sportsdata`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-sportsdata`, `typescript-models`, `typescript-testing` | +# SportsDataIO SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **SportsDataIO SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-sportsdata`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-sportsdata`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-sportsdata`, `typescript-models`, `typescript-testing` | diff --git a/plugins/sportsdata/plugin.json b/plugins/sportsdata/plugin.json index a9d6ec48..7ea47012 100644 --- a/plugins/sportsdata/plugin.json +++ b/plugins/sportsdata/plugin.json @@ -1,7 +1,7 @@ { "name": "sportsdata", "displayName": "SportsDataIO", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated SportsDataIO SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/sportsdata/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/sportsdata/skills/dotnet/dotnet-getting-started/SKILL.md index 92fca4d8..ee1f48b4 100644 --- a/plugins/sportsdata/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/sportsdata/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "SportsDataIO .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-sportsdataio is the workflow that uses it." ---- - -# Getting started with the SportsDataIO .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every SportsDataIO SDK fact. `dotnet-integrate-sportsdataio` is the workflow that drives it — you collect the contracts for the work in scope into `sports-data-io-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | SportsDataIO | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/sportsdata-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `SportsDataIo` (the `using` namespace) | -| Client class | `SportsDataIoClient` | -| Options class | `SportsDataIoClientOptions` | -| Auth | **API key** in the `Ocp-Apim-Subscription-Key` header — set `ApiKeyHeader`
**API key** in the `key` query parameter — set `ApiKeyQuery` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://api.sportsdata.io` | -| Log environment variable | `SPORTSDATAIOCLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-sportsdataio` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using SportsDataIo.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `SportsDataIoClient` | `SportsDataIo` | -| `SportsDataIoClientOptions` | `SportsDataIo` | -| `ServerEnvironment` | `SportsDataIo.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call SportsDataIO, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/SportsDataIo/Models/`, `src/SportsDataIo/Errors/` and `src/SportsDataIo/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/SportsDataIo/Models/`, enums `src/SportsDataIo/Models/Enums/`, typed errors `src/SportsDataIo/Errors/`, request records `src/SportsDataIo/Requests/`, or `src/SportsDataIo/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `sports-data-io-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/sports-data-io-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/sports-data-io-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/sports-data-io-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/sportsdata-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\sports-data-io-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/sportsdata-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `sports-data-io-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/SportsDataIo/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/SportsDataIo/Models/` (+ `src/SportsDataIo/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/SportsDataIo/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/SportsDataIo/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/SportsDataIo/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/SportsDataIo/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/SportsDataIo/Servers/`, `src/SportsDataIo/SportsDataIoClient.cs`, `src/SportsDataIo/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and SportsDataIO may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `SPORTSDATAIOCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/SportsDataIo/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-sportsdataio`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new SportsDataIoClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `SportsDataIoClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "SportsDataIO .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-sportsdataio is the workflow that uses it." +--- + +# Getting started with the SportsDataIO .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every SportsDataIO SDK fact. `dotnet-integrate-sportsdataio` is the workflow that drives it — you collect the contracts for the work in scope into `sports-data-io-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | SportsDataIO | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/sportsdata-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `SportsDataIo` (the `using` namespace) | +| Client class | `SportsDataIoClient` | +| Options class | `SportsDataIoClientOptions` | +| Auth | **API key** in the `Ocp-Apim-Subscription-Key` header — set `ApiKeyHeader`
**API key** in the `key` query parameter — set `ApiKeyQuery` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://api.sportsdata.io` | +| Log environment variable | `SPORTSDATAIOCLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-sportsdataio` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using SportsDataIo.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `SportsDataIoClient` | `SportsDataIo` | +| `SportsDataIoClientOptions` | `SportsDataIo` | +| `ServerEnvironment` | `SportsDataIo.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call SportsDataIO, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/SportsDataIo/Models/`, `src/SportsDataIo/Errors/` and `src/SportsDataIo/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/SportsDataIo/Models/`, enums `src/SportsDataIo/Models/Enums/`, typed errors `src/SportsDataIo/Errors/`, request records `src/SportsDataIo/Requests/`, or `src/SportsDataIo/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `sports-data-io-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/sports-data-io-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/sports-data-io-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/sports-data-io-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/sportsdata-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\sports-data-io-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/sportsdata-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `sports-data-io-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/SportsDataIo/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/SportsDataIo/Models/` (+ `src/SportsDataIo/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/SportsDataIo/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/SportsDataIo/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/SportsDataIo/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/SportsDataIo/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/SportsDataIo/Servers/`, `src/SportsDataIo/SportsDataIoClient.cs`, `src/SportsDataIo/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and SportsDataIO may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `SPORTSDATAIOCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/SportsDataIo/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-sportsdataio`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new SportsDataIoClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `SportsDataIoClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/sportsdata/skills/python/python-getting-started/SKILL.md b/plugins/sportsdata/skills/python/python-getting-started/SKILL.md index 65ee6e67..919c08ec 100644 --- a/plugins/sportsdata/skills/python/python-getting-started/SKILL.md +++ b/plugins/sportsdata/skills/python/python-getting-started/SKILL.md @@ -1,271 +1,271 @@ ---- -name: "python-getting-started" -description: "SportsDataIO Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any SportsDataIO Python SDK contract question or writing any SDK code." ---- - -# Getting started with the SportsDataIO Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing SportsDataIO Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-sportsdataio` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `sports_data_io/` and `pyproject.toml` of the generated package at version `1.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | SportsDataIO | -| Distribution name (what you install) | `sports-data-io` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `sports_data_io` — not the string you install | -| Source repository | https://github.com/context-plugins/sportsdata-python-sdk | -| Source branch | `main` | -| Version | `1.0` | -| Sync client class | `SportsDataIoClient` (alias `Client`) | -| Async client class | `AsyncSportsDataIoClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key_header` · `api_key_query`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **API key** in the `Ocp-Apim-Subscription-Key` header — set `api_key_header` · **API key** in the `key` query parameter — set `api_key_query` | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.sportsdata.io` | -| Base-URL config | `ServerConfig` (`sports_data_io/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 741 operations across 50 controllers · 406 models · 0 unions · 5 enums · 741 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "sports-data-io @ git+https://github.com/context-plugins/sportsdata-python-sdk.git@main" -python -c "import sports_data_io, pathlib; print(pathlib.Path(sports_data_io.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from sports_data_io import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`sports_data_io/__init__.py` exports exactly 5 names beside the `sports_data_io.models` subpackage it re-exports: - -```python -from sports_data_io import ( - AsyncClient, - AsyncSportsDataIoClient, - Client, - ServerConfig, - SportsDataIoClient, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `sports_data_io.models` | -| Enums (and their open `…OrStr` aliases) | `sports_data_io.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `sports_data_io.core` | -| Per-operation error *unions* | `sports_data_io.errors` (`CbbV3OddsAlternateMarketPreGameOddsByDateErrorBody`, …) | - -`sports_data_io.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`sports_data_io/server/server_config.py`), and its default is **`https://api.sportsdata.io`**: - -```python -base_url: str = "https://api.sportsdata.io" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://api.sportsdata.io`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (two schemes) - -An API key sent as the `Ocp-Apim-Subscription-Key` header, exposed as the client's `api_key_header=` keyword taking a plain string. - -```python -from sports_data_io import Client - -client = Client(api_key_header="") -``` - -An API key sent as the `key` query parameter, exposed as the client's `api_key_query=` keyword taking a plain string. - -```python -from sports_data_io import Client - -client = Client(api_key_query="") -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.cbb_v3_odds` | `CbbV3Odds` / `AsyncCbbV3Odds` | 34 | `cbb_v3_odds_alternate_market_pre_game_odds_by_date` · `cbb_v3_odds_alternate_market_pre_game_odds_line_movement` · `cbb_v3_odds_betting_events_by_date` · `cbb_v3_odds_betting_events_by_season` · `cbb_v3_odds_betting_futures_by_season` · `cbb_v3_odds_betting_futures_by_season_sportsbook_group` · … | -| `client.cbb_v3_scores` | `CbbV3Scores` / `AsyncCbbV3Scores` | 22 | `cbb_v3_scores_are_games_in_progress` · `cbb_v3_scores_games_by_date_final` · `cbb_v3_scores_games_by_date_live_final` · `cbb_v3_scores_games_by_season` · `cbb_v3_scores_games_by_season_final` · `cbb_v3_scores_games_basic_by_date` · … | -| `client.cbb_v3_stats` | `CbbV3Stats` / `AsyncCbbV3Stats` | 11 | `cbb_v3_stats_box_score_final` · `cbb_v3_stats_box_score_live_final` · `cbb_v3_stats_box_scores_by_date_final` · `cbb_v3_stats_box_scores_by_date_live_final` · `cbb_v3_stats_box_scores_delta_by_date` · `cbb_v3_stats_player_game_logs_by_season` · … | -| `client.cfb_v3_odds` | `CfbV3Odds` / `AsyncCfbV3Odds` | 34 | `cfb_v3_odds_betting_events_by_date` · `cfb_v3_odds_betting_events_by_season` · `cfb_v3_odds_betting_futures_by_season` · `cfb_v3_odds_betting_futures_by_season_sportsbook_group` · `cfb_v3_odds_betting_market` · `cfb_v3_odds_betting_market_sportsbook_group` · … | -| `client.cfb_v3_scores` | `CfbV3Scores` / `AsyncCfbV3Scores` | 26 | `cfb_v3_scores_are_games_in_progress` · `cfb_v3_scores_conference_hierarchy` · `cfb_v3_scores_games_by_date_live_final` · `cfb_v3_scores_games_by_week_final` · `cfb_v3_scores_games_by_week_live_final` · `cfb_v3_scores_games_basic_by_date_live_final` · … | -| `client.cfb_v3_stats` | `CfbV3Stats` / `AsyncCfbV3Stats` | 13 | `cfb_v3_stats_box_score_final` · `cfb_v3_stats_box_score_live_final` · `cfb_v3_stats_box_scores_by_date_final` · `cfb_v3_stats_box_scores_by_date_live_final` · `cfb_v3_stats_box_scores_by_week_final` · `cfb_v3_stats_box_scores_by_week_live_final` · … | -| `client.cwbb_v3_scores` | `CwbbV3Scores` / `AsyncCwbbV3Scores` | 8 | `cwbb_v3_scores_are_any_games_in_progress` · `cwbb_v3_scores_games_by_date_final` · `cwbb_v3_scores_games_by_date_live_final` · `cwbb_v3_scores_league_hierarchy` · `cwbb_v3_scores_schedules` · `cwbb_v3_scores_schedules_basic` · … | -| `client.golf_v2` | `GolfV2` / `AsyncGolfV2` | 23 | `golf_v2_courses_courses` · `golf_v2_currentseason_season_current` · `golf_v2_dfsslateownershipprojectionsbyslateid_dfs_slate_ownership_projections_by_slate` · `golf_v2_dfsslatesbytournament_dfs_slates_by_tournament` · `golf_v2_fantasygamestatsbytournament_fantasy_points_by_tournament` · `golf_v2_leaderboard_leaderboard_live_final` · … | -| `client.golf_v3_headshots` | `GolfV3Headshots` / `AsyncGolfV3Headshots` | 1 | `golf_v3_headshots_headshots` | -| `client.golf_v3_odds` | `GolfV3Odds` / `AsyncGolfV3Odds` | 21 | `golf_v3_odds_betting_events_by_date` · `golf_v3_odds_betting_events_by_season` · `golf_v3_odds_betting_market` · `golf_v3_odds_betting_market_sportsbook_group` · `golf_v3_odds_betting_markets_by_event` · `golf_v3_odds_betting_markets_by_event_sportsbook_group` · … | -| `client.golf_v3_roto_baller_premium_news` | `GolfV3RotoBallerPremiumNews` / `AsyncGolfV3RotoBallerPremiumNews` | 2 | `golf_v3_news_rotoballer_premium_news` · `golf_v3_news_rotoballer_premium_news_by_date` | -| `client.mlb_v3_headshots` | `MlbV3Headshots` / `AsyncMlbV3Headshots` | 1 | `mlb_v3_headshots_headshots` | -| `client.mlb_v3_odds` | `MlbV3Odds` / `AsyncMlbV3Odds` | 34 | `mlb_v3_odds_betting_events_by_date` · `mlb_v3_odds_betting_events_by_season` · `mlb_v3_odds_betting_futures_by_season` · `mlb_v3_odds_betting_futures_by_season_sportsbook_group` · `mlb_v3_odds_betting_market` · `mlb_v3_odds_betting_market_sportsbook_group` · … | -| `client.mlb_v3_play_by_play` | `MlbV3PlayByPlay` / `AsyncMlbV3PlayByPlay` | 3 | `mlb_v3_pbp_play_by_play_final` · `mlb_v3_pbp_play_by_play_live_final` · `mlb_v3_pbp_play_by_play_delta` | -| `client.mlb_v3_projections` | `MlbV3Projections` / `AsyncMlbV3Projections` | 8 | `mlb_v3_projections_depth_charts` · `mlb_v3_projections_dfs_slate_ownership_projections_by_slate` · `mlb_v3_projections_dfs_slate_ownership_projections_upcoming` · `mlb_v3_projections_dfs_slates_by_date` · `mlb_v3_projections_player_details_by_injured` · `mlb_v3_projections_projected_player_game_stats_by_date` · … | -| `client.mlb_v3_roto_baller_premium_news` | `MlbV3RotoBallerPremiumNews` / `AsyncMlbV3RotoBallerPremiumNews` | 2 | `mlb_v3_news_rotoballer_premium_news` · `mlb_v3_news_rotoballer_premium_news_by_date` | -| `client.mlb_v3_scores` | `MlbV3Scores` / `AsyncMlbV3Scores` | 26 | `mlb_v3_scores_are_games_in_progress` · `mlb_v3_scores_games_by_date_final` · `mlb_v3_scores_games_by_date_live_final` · `mlb_v3_scores_games_basic_by_date_final` · `mlb_v3_scores_games_basic_by_date_live_final` · `mlb_v3_scores_news` · … | -| `client.mlb_v3_stats` | `MlbV3Stats` / `AsyncMlbV3Stats` | 17 | `mlb_v3_stats_batter_vs_pitcher_stats` · `mlb_v3_stats_box_score_final` · `mlb_v3_stats_box_score_live_final` · `mlb_v3_stats_box_scores_by_date_final` · `mlb_v3_stats_box_scores_by_date_live_final` · `mlb_v3_stats_box_scores_delta_by_date` · … | -| `client.mma_v3_odds` | `MmaV3Odds` / `AsyncMmaV3Odds` | 10 | `mma_v3_odds_betting_events_by_event` · `mma_v3_odds_betting_events_upcoming` · `mma_v3_odds_betting_fighter_props_by_event` · `mma_v3_odds_betting_market` · `mma_v3_odds_betting_markets_by_event` · `mma_v3_odds_betting_markets_by_market_type` · … | -| `client.mma_v3_scores` | `MmaV3Scores` / `AsyncMmaV3Scores` | 6 | `mma_v3_scores_event` · `mma_v3_scores_fighter_profile_by_fighter` · `mma_v3_scores_fighter_profiles_all` · `mma_v3_scores_fighter_profiles_basic_all` · `mma_v3_scores_leagues` · `mma_v3_scores_schedules` | -| `client.mma_v3_stats` | `MmaV3Stats` / `AsyncMmaV3Stats` | 4 | `mma_v3_stats_fight_basic_final` · `mma_v3_stats_fight_basic_live_final` · `mma_v3_stats_fight_final` · `mma_v3_stats_fight_live_final` | -| `client.nascar_v2` | `NascarV2` / `AsyncNascarV2` | 13 | `nascar_v2_driver_driver_profiles_by_driver` · `nascar_v2_driverraceprojections_projected_player_race_stats_by_race` · `nascar_v2_drivers_driver_profiles_all` · `nascar_v2_driversbyactive_drivers_by_active` · `nascar_v2_entrylistbyrace_entry_list_by_race` · `nascar_v2_fantasygamestatsbyrace_fantasy_points_by_race` · … | -| `client.nascar_v3_odds` | `NascarV3Odds` / `AsyncNascarV3Odds` | 10 | `nascar_v3_odds_betting_events_by_date` · `nascar_v3_odds_betting_events_by_season` · `nascar_v3_odds_betting_market` · `nascar_v3_odds_betting_markets_by_event` · `nascar_v3_odds_betting_markets_by_market_type` · `nascar_v3_odds_betting_markets_by_race` · … | -| `client.nba_v3_headshots` | `NbaV3Headshots` / `AsyncNbaV3Headshots` | 1 | `nba_v3_headshots_headshots` | -| `client.nba_v3_odds` | `NbaV3Odds` / `AsyncNbaV3Odds` | 34 | `nba_v3_odds_betting_events_by_date` · `nba_v3_odds_betting_events_by_season` · `nba_v3_odds_betting_futures_by_season` · `nba_v3_odds_betting_futures_by_season_sportsbook_group` · `nba_v3_odds_betting_market` · `nba_v3_odds_betting_market_sportsbook_group` · … | -| `client.nba_v3_play_by_play` | `NbaV3PlayByPlay` / `AsyncNbaV3PlayByPlay` | 3 | `nba_v3_pbp_play_by_play_final` · `nba_v3_pbp_play_by_play_live_final` · `nba_v3_pbp_play_by_play_delta` | -| `client.nba_v3_projections` | `NbaV3Projections` / `AsyncNbaV3Projections` | 6 | `nba_v3_projections_dfs_slates_by_date` · `nba_v3_projections_player_details_by_injured` · `nba_v3_projections_projected_player_game_stats_by_date` · `nba_v3_projections_projected_player_season_stats` · `nba_v3_projections_projected_player_season_stats_by_team` · `nba_v3_projections_starting_lineups_by_date` | -| `client.nba_v3_roto_baller_premium_news` | `NbaV3RotoBallerPremiumNews` / `AsyncNbaV3RotoBallerPremiumNews` | 2 | `nba_v3_news_rotoballer_premium_news` · `nba_v3_news_rotoballer_premium_news_by_date` | -| `client.nba_v3_scores` | `NbaV3Scores` / `AsyncNbaV3Scores` | 27 | `nba_v3_scores_are_games_in_progress` · `nba_v3_scores_depth_charts` · `nba_v3_scores_games_by_date_final` · `nba_v3_scores_games_by_date_live_final` · `nba_v3_scores_games_basic_by_date_final` · `nba_v3_scores_games_basic_by_date_live_final` · … | -| `client.nba_v3_stats` | `NbaV3Stats` / `AsyncNbaV3Stats` | 14 | `nba_v3_stats_all_stars` · `nba_v3_stats_box_score_final` · `nba_v3_stats_box_score_live_final` · `nba_v3_stats_box_scores_by_date_final` · `nba_v3_stats_box_scores_by_date_live_final` · `nba_v3_stats_box_scores_delta_by_date` · … | -| `client.nfl_v3_headshots` | `NflV3Headshots` / `AsyncNflV3Headshots` | 1 | `nfl_v3_headshots_headshots` | -| `client.nfl_v3_odds` | `NflV3Odds` / `AsyncNflV3Odds` | 34 | `nfl_v3_odds_betting_events_by_date` · `nfl_v3_odds_betting_events_by_season` · `nfl_v3_odds_betting_futures_by_season` · `nfl_v3_odds_betting_futures_by_season_sportsbook_group` · `nfl_v3_odds_betting_market` · `nfl_v3_odds_betting_market_sportsbook_group` · … | -| `client.nfl_v3_play_by_play` | `NflV3PlayByPlay` / `AsyncNflV3PlayByPlay` | 6 | `nfl_v3_pbp_play_by_play_by_team_final` · `nfl_v3_pbp_play_by_play_by_team_live_final` · `nfl_v3_pbp_play_by_play_final` · `nfl_v3_pbp_play_by_play_live_final` · `nfl_v3_pbp_play_by_play_delta` · `nfl_v3_pbp_play_by_play_simulation` | -| `client.nfl_v3_projections` | `NflV3Projections` / `AsyncNflV3Projections` | 13 | `nfl_v3_projections_dfs_slate_ownership_projections_by_slate` · `nfl_v3_projections_dfs_slate_ownership_projections_upcoming` · `nfl_v3_projections_dfs_slates_by_date` · `nfl_v3_projections_dfs_slates_by_week` · `nfl_v3_projections_idp_projected_player_game_stats_by_team` · `nfl_v3_projections_idp_projected_player_game_stats_by_week` · … | -| `client.nfl_v3_roto_baller_premium_news` | `NflV3RotoBallerPremiumNews` / `AsyncNflV3RotoBallerPremiumNews` | 3 | `nfl_v3_news_rotoballer_premium_news` · `nfl_v3_news_rotoballer_premium_news_by_date` · `nfl_v3_news_rotoballer_premium_news_by_team` | -| `client.nfl_v3_scores` | `NflV3Scores` / `AsyncNflV3Scores` | 44 | `nfl_v3_scores_are_games_in_progress` · `nfl_v3_scores_bye_weeks` · `nfl_v3_scores_depth_charts_all` · `nfl_v3_scores_depth_charts_by_active` · `nfl_v3_scores_depth_charts_by_week` · `nfl_v3_scores_games_by_date_final` · … | -| `client.nfl_v3_stats` | `NflV3Stats` / `AsyncNflV3Stats` | 34 | `nfl_v3_stats_box_score_by_team_final` · `nfl_v3_stats_box_score_by_team_live_final` · `nfl_v3_stats_box_score_final` · `nfl_v3_stats_box_score_live_final` · `nfl_v3_stats_box_scores_by_week_final` · `nfl_v3_stats_box_scores_delta_by_week` · … | -| `client.nhl_v3_headshots` | `NhlV3Headshots` / `AsyncNhlV3Headshots` | 1 | `nhl_v3_headshots_headshots` | -| `client.nhl_v3_odds` | `NhlV3Odds` / `AsyncNhlV3Odds` | 34 | `nhl_v3_odds_betting_events_by_date` · `nhl_v3_odds_betting_events_by_season` · `nhl_v3_odds_betting_futures_by_season` · `nhl_v3_odds_betting_futures_by_season_sportsbook_group` · `nhl_v3_odds_betting_market` · `nhl_v3_odds_betting_market_sportsbook_group` · … | -| `client.nhl_v3_play_by_play` | `NhlV3PlayByPlay` / `AsyncNhlV3PlayByPlay` | 3 | `nhl_v3_pbp_play_by_play_final` · `nhl_v3_pbp_play_by_play_live_final` · `nhl_v3_pbp_play_by_play_delta` | -| `client.nhl_v3_projections` | `NhlV3Projections` / `AsyncNhlV3Projections` | 4 | `nhl_v3_projections_dfs_slates_by_date` · `nhl_v3_projections_player_details_by_injured` · `nhl_v3_projections_projected_player_game_stats_by_date` · `nhl_v3_projections_starting_goaltenders_by_date` | -| `client.nhl_v3_roto_baller_premium_news` | `NhlV3RotoBallerPremiumNews` / `AsyncNhlV3RotoBallerPremiumNews` | 2 | `nhl_v3_news_rotoballer_premium_news` · `nhl_v3_news_rotoballer_premium_news_by_date` | -| `client.nhl_v3_scores` | `NhlV3Scores` / `AsyncNhlV3Scores` | 27 | `nhl_v3_scores_are_games_in_progress` · `nhl_v3_scores_depth_charts_goalies` · `nhl_v3_scores_games_by_date_final` · `nhl_v3_scores_games_by_date_live_final` · `nhl_v3_scores_games_basic_by_date_final` · `nhl_v3_scores_games_basic_by_date_live_final` · … | -| `client.nhl_v3_stats` | `NhlV3Stats` / `AsyncNhlV3Stats` | 14 | `nhl_v3_stats_box_score_final` · `nhl_v3_stats_box_score_live_final` · `nhl_v3_stats_box_scores_by_date_final` · `nhl_v3_stats_box_scores_by_date_live_final` · `nhl_v3_stats_box_scores_delta_by_date` · `nhl_v3_stats_fantasy_points_by_date` · … | -| `client.soccer_v4_headshots` | `SoccerV4Headshots` / `AsyncSoccerV4Headshots` | 1 | `soccer_v4_headshots_headshots` | -| `client.soccer_v4_odds` | `SoccerV4Odds` / `AsyncSoccerV4Odds` | 28 | `soccer_v4_odds_betting_events_by_date` · `soccer_v4_odds_betting_events_by_season` · `soccer_v4_odds_betting_futures_by_season` · `soccer_v4_odds_betting_futures_by_season_sportsbook_group` · `soccer_v4_odds_betting_market` · `soccer_v4_odds_betting_market_sportsbook_group` · … | -| `client.soccer_v4_projections` | `SoccerV4Projections` / `AsyncSoccerV4Projections` | 4 | `soccer_v4_projections_dfs_slates_by_date` · `soccer_v4_projections_player_details_by_injured` · `soccer_v4_projections_projected_player_game_stats_by_date` · `soccer_v4_projections_upcoming_dfs_slates_by_competition` | -| `client.soccer_v4_scores` | `SoccerV4Scores` / `AsyncSoccerV4Scores` | 21 | `soccer_v4_scores_areas_countries` · `soccer_v4_scores_competitions_leagues` · `soccer_v4_scores_games_by_competition_live_final` · `soccer_v4_scores_games_by_date_final` · `soccer_v4_scores_games_by_date_live_final` · `soccer_v4_scores_games_basic_by_date_final` · … | -| `client.soccer_v4_stats` | `SoccerV4Stats` / `AsyncSoccerV4Stats` | 11 | `soccer_v4_stats_box_score_final` · `soccer_v4_stats_box_score_live_final` · `soccer_v4_stats_box_scores_by_date_final` · `soccer_v4_stats_box_scores_by_date_live_final` · `soccer_v4_stats_box_scores_delta_by_date` · `soccer_v4_stats_fantasy_points_by_date` · … | -| `client.wnba_v3_scores` | `WnbaV3Scores` / `AsyncWnbaV3Scores` | 40 | `wnba_v3_scores_are_games_in_progress` · `wnba_v3_scores_betting_events_by_date` · `wnba_v3_scores_betting_events_by_season` · `wnba_v3_scores_betting_futures_by_season` · `wnba_v3_scores_betting_futures_by_season_sportsbook_group` · `wnba_v3_scores_betting_market` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `sports_data_io/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `sports_data_io/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/sportsdata-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `sports_data_io/models/area.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `sports_data_io/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import sports_data_io, pathlib; print(pathlib.Path(sports_data_io.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/sports_data_io` on Windows, `.venv/lib/python3.*/site-packages/sports_data_io` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `api_key_header=` and `api_key_query=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **1 operation returns `None`**, so `with_raw_response` is the only way to observe its status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 1 operation that returns `None`** — `wnba_v3_scores.wnba_v3_scores_standings`. Its raw peer is `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — there is **one** typed error body in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `Unauthorized` — 741 operations (`cbb_v3_odds.cbb_v3_odds_alternate_market_pre_game_odds_by_date` · `cbb_v3_odds.cbb_v3_odds_alternate_market_pre_game_odds_line_movement` · `cbb_v3_odds.cbb_v3_odds_betting_events_by_date` · `cbb_v3_odds.cbb_v3_odds_betting_events_by_season` · `cbb_v3_odds.cbb_v3_odds_betting_futures_by_season` · `cbb_v3_odds.cbb_v3_odds_betting_futures_by_season_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_market` · `cbb_v3_odds.cbb_v3_odds_betting_market_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_event` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_event_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_game` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_game_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_market_type` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_market_type_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_metadata` · `cbb_v3_odds.cbb_v3_odds_betting_player_props_by_game` · `cbb_v3_odds.cbb_v3_odds_betting_player_props_by_game_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_results_by_market` · `cbb_v3_odds.cbb_v3_odds_betting_results_by_market_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_splits_by_betting_market` · `cbb_v3_odds.cbb_v3_odds_betting_splits_by_game` · `cbb_v3_odds.cbb_v3_odds_betting_trends_by_matchup` · `cbb_v3_odds.cbb_v3_odds_betting_trends_by_team` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_by_date` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_by_date_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_line_movement` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_line_movement_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_odds_by_date` · `cbb_v3_odds.cbb_v3_odds_pre_game_odds_line_movement` · `cbb_v3_odds.cbb_v3_odds_sportsbooks_by_active` · `cbb_v3_scores.cbb_v3_scores_are_games_in_progress` · `cbb_v3_scores.cbb_v3_scores_games_by_date_final` · `cbb_v3_scores.cbb_v3_scores_games_by_date_live_final` · `cbb_v3_scores.cbb_v3_scores_games_by_season` · `cbb_v3_scores.cbb_v3_scores_games_by_season_final` · `cbb_v3_scores.cbb_v3_scores_games_basic_by_date` · `cbb_v3_scores.cbb_v3_scores_league_hierarchy` · `cbb_v3_scores.cbb_v3_scores_player_details_by_active` · `cbb_v3_scores.cbb_v3_scores_player_details_by_active2` · `cbb_v3_scores.cbb_v3_scores_player_details_by_injured` · `cbb_v3_scores.cbb_v3_scores_player_details_by_team` · `cbb_v3_scores.cbb_v3_scores_player_profiles_by_team` · `cbb_v3_scores.cbb_v3_scores_schedules_by_team` · `cbb_v3_scores.cbb_v3_scores_schedules_basic` · `cbb_v3_scores.cbb_v3_scores_season_current` · `cbb_v3_scores.cbb_v3_scores_stadiums` · `cbb_v3_scores.cbb_v3_scores_team_game_logs_by_season` · `cbb_v3_scores.cbb_v3_scores_team_game_stats_by_date_live_final` · `cbb_v3_scores.cbb_v3_scores_team_profiles_all` · `cbb_v3_scores.cbb_v3_scores_team_season_stats` · `cbb_v3_scores.cbb_v3_scores_teams_basic_all` · `cbb_v3_scores.cbb_v3_scores_tournament_hierarchy` · `cbb_v3_stats.cbb_v3_stats_box_score_final` · `cbb_v3_stats.cbb_v3_stats_box_score_live_final` · `cbb_v3_stats.cbb_v3_stats_box_scores_by_date_final` · `cbb_v3_stats.cbb_v3_stats_box_scores_by_date_live_final` · `cbb_v3_stats.cbb_v3_stats_box_scores_delta_by_date` · `cbb_v3_stats.cbb_v3_stats_player_game_logs_by_season` · `cbb_v3_stats.cbb_v3_stats_player_game_stats_by_date_final` · `cbb_v3_stats.cbb_v3_stats_player_game_stats_by_date_live_final` · `cbb_v3_stats.cbb_v3_stats_player_season_stats` · `cbb_v3_stats.cbb_v3_stats_player_season_stats_by_team` · `cbb_v3_stats.cbb_v3_stats_team_game_stats_by_date_final` · `cfb_v3_odds.cfb_v3_odds_betting_events_by_date` · `cfb_v3_odds.cfb_v3_odds_betting_events_by_season` · `cfb_v3_odds.cfb_v3_odds_betting_futures_by_season` · `cfb_v3_odds.cfb_v3_odds_betting_futures_by_season_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_market` · `cfb_v3_odds.cfb_v3_odds_betting_market_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_event` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_event_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_game` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_game_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_market_type` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_market_type_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_metadata` · `cfb_v3_odds.cfb_v3_odds_betting_player_props_by_game` · `cfb_v3_odds.cfb_v3_odds_betting_player_props_by_game_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_results_by_market` · `cfb_v3_odds.cfb_v3_odds_betting_results_by_market_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_splits_by_betting_market` · `cfb_v3_odds.cfb_v3_odds_betting_splits_by_game` · `cfb_v3_odds.cfb_v3_odds_betting_trends_by_matchup` · `cfb_v3_odds.cfb_v3_odds_betting_trends_by_team` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_by_week` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_by_week_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_line_movement` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_line_movement_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_period_game_odds_by_week` · `cfb_v3_odds.cfb_v3_odds_period_game_odds_line_movement` · `cfb_v3_odds.cfb_v3_odds_pre_game_and_period_game_odds_by_week_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_pre_game_odds_by_week` · `cfb_v3_odds.cfb_v3_odds_pre_game_odds_line_movement` · `cfb_v3_odds.cfb_v3_odds_sportsbooks_by_active` · `cfb_v3_scores.cfb_v3_scores_are_games_in_progress` · `cfb_v3_scores.cfb_v3_scores_conference_hierarchy` · `cfb_v3_scores.cfb_v3_scores_games_by_date_live_final` · `cfb_v3_scores.cfb_v3_scores_games_by_week_final` · `cfb_v3_scores.cfb_v3_scores_games_by_week_live_final` · `cfb_v3_scores.cfb_v3_scores_games_basic_by_date_live_final` · `cfb_v3_scores.cfb_v3_scores_games_basic_by_week_final` · `cfb_v3_scores.cfb_v3_scores_games_basic_by_date_final` · `cfb_v3_scores.cfb_v3_scores_games_by_date_final` · `cfb_v3_scores.cfb_v3_scores_player_details_by_active` · `cfb_v3_scores.cfb_v3_scores_player_details_by_active2` · `cfb_v3_scores.cfb_v3_scores_player_details_by_injured` · `cfb_v3_scores.cfb_v3_scores_player_details_by_team` · `cfb_v3_scores.cfb_v3_scores_player_profiles_by_team` · `cfb_v3_scores.cfb_v3_scores_schedules` · `cfb_v3_scores.cfb_v3_scores_schedules_basic` · `cfb_v3_scores.cfb_v3_scores_season_current` · `cfb_v3_scores.cfb_v3_scores_season_current_details` · `cfb_v3_scores.cfb_v3_scores_season_type_current` · `cfb_v3_scores.cfb_v3_scores_stadiums` · `cfb_v3_scores.cfb_v3_scores_team_game_logs_by_season` · `cfb_v3_scores.cfb_v3_scores_team_game_stats_by_week_live_final` · `cfb_v3_scores.cfb_v3_scores_team_profiles_all` · `cfb_v3_scores.cfb_v3_scores_team_season_stats_standings` · `cfb_v3_scores.cfb_v3_scores_teams_basic_all` · `cfb_v3_scores.cfb_v3_scores_week_current` · `cfb_v3_stats.cfb_v3_stats_box_score_final` · `cfb_v3_stats.cfb_v3_stats_box_score_live_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_date_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_date_live_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_week_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_week_live_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_delta_by_week` · `cfb_v3_stats.cfb_v3_stats_player_game_logs_by_season` · `cfb_v3_stats.cfb_v3_stats_player_game_stats_by_week_final` · `cfb_v3_stats.cfb_v3_stats_player_game_stats_by_week_live_final` · `cfb_v3_stats.cfb_v3_stats_player_season_stats` · `cfb_v3_stats.cfb_v3_stats_player_season_stats_by_team` · `cfb_v3_stats.cfb_v3_stats_team_game_stats_by_week_final` · `cwbb_v3_scores.cwbb_v3_scores_are_any_games_in_progress` · `cwbb_v3_scores.cwbb_v3_scores_games_by_date_final` · `cwbb_v3_scores.cwbb_v3_scores_games_by_date_live_final` · `cwbb_v3_scores.cwbb_v3_scores_league_hierarchy` · `cwbb_v3_scores.cwbb_v3_scores_schedules` · `cwbb_v3_scores.cwbb_v3_scores_schedules_basic` · `cwbb_v3_scores.cwbb_v3_scores_season_current` · `cwbb_v3_scores.cwbb_v3_scores_team_profiles_all` · `golf_v2.golf_v2_courses_courses` · `golf_v2.golf_v2_currentseason_season_current` · `golf_v2.golf_v2_dfsslateownershipprojectionsbyslateid_dfs_slate_ownership_projections_by_slate` · `golf_v2.golf_v2_dfsslatesbytournament_dfs_slates_by_tournament` · `golf_v2.golf_v2_fantasygamestatsbytournament_fantasy_points_by_tournament` · `golf_v2.golf_v2_leaderboard_leaderboard_live_final` · `golf_v2.golf_v2_leaderboardbasic_leaderboard_basic` · `golf_v2.golf_v2_leaderboardbasicfinal_leaderboard_basic_final` · `golf_v2.golf_v2_leaderboardfinal_leaderboard_final` · `golf_v2.golf_v2_news_news` · `golf_v2.golf_v2_newsbydate_news_by_date` · `golf_v2.golf_v2_player_player` · `golf_v2.golf_v2_players_player_profiles_all` · `golf_v2.golf_v2_playerseasonstats_player_season_stats` · `golf_v2.golf_v2_playertournamentholescores_player_hole_scores_by_tournament_live_final` · `golf_v2.golf_v2_playertournamentholescoresfinal_player_hole_scores_by_tournament_final` · `golf_v2.golf_v2_playertournamentprojectionstats_player_projected_stats_by_tournament` · `golf_v2.golf_v2_playertournamentroundscores_scores_by_tournament_live_final` · `golf_v2.golf_v2_playertournamentroundscoresfinal_scores_by_tournament_final` · `golf_v2.golf_v2_rankings_rankings` · `golf_v2.golf_v2_tournaments_schedule_by_season` · `golf_v2.golf_v2_tournaments_schedules` · `golf_v2.golf_v2_upcomingdfsslateownershipprojections_dfs_slate_ownership_projections_upcoming` · `golf_v3_headshots.golf_v3_headshots_headshots` · `golf_v3_odds.golf_v3_odds_betting_events_by_date` · `golf_v3_odds.golf_v3_odds_betting_events_by_season` · `golf_v3_odds.golf_v3_odds_betting_market` · `golf_v3_odds.golf_v3_odds_betting_market_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_markets_by_event` · `golf_v3_odds.golf_v3_odds_betting_markets_by_event_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_markets_by_market_type` · `golf_v3_odds.golf_v3_odds_betting_markets_by_market_type_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_markets_by_tournament` · `golf_v3_odds.golf_v3_odds_betting_markets_by_tournament_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_metadata` · `golf_v3_odds.golf_v3_odds_betting_player_props_by_tournament_sportsbook_group` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds_sportsbook_group` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds_line_movement` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds_line_movement_sportsbook_group` · `golf_v3_odds.golf_v3_odds_sportsbooks_by_active` · `golf_v3_odds.golf_v3_odds_tournament_odds` · `golf_v3_odds.golf_v3_odds_tournament_odds_sportsbook_group` · `golf_v3_odds.golf_v3_odds_tournament_odds_line_movement` · `golf_v3_odds.golf_v3_odds_tournament_odds_line_movement_sportsbook_group` · `golf_v3_roto_baller_premium_news.golf_v3_news_rotoballer_premium_news` · `golf_v3_roto_baller_premium_news.golf_v3_news_rotoballer_premium_news_by_date` · `mlb_v3_headshots.mlb_v3_headshots_headshots` · `mlb_v3_odds.mlb_v3_odds_betting_events_by_date` · `mlb_v3_odds.mlb_v3_odds_betting_events_by_season` · `mlb_v3_odds.mlb_v3_odds_betting_futures_by_season` · `mlb_v3_odds.mlb_v3_odds_betting_futures_by_season_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_market` · `mlb_v3_odds.mlb_v3_odds_betting_market_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_event` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_event_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_game` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_game_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_market_type` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_market_type_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_metadata` · `mlb_v3_odds.mlb_v3_odds_betting_player_props_by_game` · `mlb_v3_odds.mlb_v3_odds_betting_player_props_by_game_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_results_by_market` · `mlb_v3_odds.mlb_v3_odds_betting_results_by_market_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_splits_by_game` · `mlb_v3_odds.mlb_v3_odds_betting_splits_by_betting_market` · `mlb_v3_odds.mlb_v3_odds_betting_trends_by_matchup` · `mlb_v3_odds.mlb_v3_odds_betting_trends_by_team` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_by_date` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_by_date_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_line_movement` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_line_movement_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_period_game_odds_by_date` · `mlb_v3_odds.mlb_v3_odds_period_game_odds_line_movement` · `mlb_v3_odds.mlb_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_pre_game_odds_by_date` · `mlb_v3_odds.mlb_v3_odds_pre_game_odds_line_movement` · `mlb_v3_odds.mlb_v3_odds_sportsbooks_by_active` · `mlb_v3_play_by_play.mlb_v3_pbp_play_by_play_final` · `mlb_v3_play_by_play.mlb_v3_pbp_play_by_play_live_final` · `mlb_v3_play_by_play.mlb_v3_pbp_play_by_play_delta` · `mlb_v3_projections.mlb_v3_projections_depth_charts` · `mlb_v3_projections.mlb_v3_projections_dfs_slate_ownership_projections_by_slate` · `mlb_v3_projections.mlb_v3_projections_dfs_slate_ownership_projections_upcoming` · `mlb_v3_projections.mlb_v3_projections_dfs_slates_by_date` · `mlb_v3_projections.mlb_v3_projections_player_details_by_injured` · `mlb_v3_projections.mlb_v3_projections_projected_player_game_stats_by_date` · `mlb_v3_projections.mlb_v3_projections_projected_player_season_stats_with_adp` · `mlb_v3_projections.mlb_v3_projections_starting_lineups_by_date` · `mlb_v3_roto_baller_premium_news.mlb_v3_news_rotoballer_premium_news` · `mlb_v3_roto_baller_premium_news.mlb_v3_news_rotoballer_premium_news_by_date` · `mlb_v3_scores.mlb_v3_scores_are_games_in_progress` · `mlb_v3_scores.mlb_v3_scores_games_by_date_final` · `mlb_v3_scores.mlb_v3_scores_games_by_date_live_final` · `mlb_v3_scores.mlb_v3_scores_games_basic_by_date_final` · `mlb_v3_scores.mlb_v3_scores_games_basic_by_date_live_final` · `mlb_v3_scores.mlb_v3_scores_news` · `mlb_v3_scores.mlb_v3_scores_news_by_date` · `mlb_v3_scores.mlb_v3_scores_player_details_by_active` · `mlb_v3_scores.mlb_v3_scores_player_details_by_free_agents` · `mlb_v3_scores.mlb_v3_scores_player_profiles_by_active` · `mlb_v3_scores.mlb_v3_scores_player_profiles_by_free_agents` · `mlb_v3_scores.mlb_v3_scores_player_profiles_by_team` · `mlb_v3_scores.mlb_v3_scores_players_details_by_team` · `mlb_v3_scores.mlb_v3_scores_schedules` · `mlb_v3_scores.mlb_v3_scores_schedules_basic` · `mlb_v3_scores.mlb_v3_scores_season_current` · `mlb_v3_scores.mlb_v3_scores_stadiums` · `mlb_v3_scores.mlb_v3_scores_standings` · `mlb_v3_scores.mlb_v3_scores_team_game_logs_by_season` · `mlb_v3_scores.mlb_v3_scores_team_game_stats_by_date_final` · `mlb_v3_scores.mlb_v3_scores_team_game_stats_by_date_live_final` · `mlb_v3_scores.mlb_v3_scores_team_profiles_all` · `mlb_v3_scores.mlb_v3_scores_team_profiles_by_active` · `mlb_v3_scores.mlb_v3_scores_team_profiles_by_season` · `mlb_v3_scores.mlb_v3_scores_team_season_stats` · `mlb_v3_scores.mlb_v3_scores_transactions` · `mlb_v3_stats.mlb_v3_stats_batter_vs_pitcher_stats` · `mlb_v3_stats.mlb_v3_stats_box_score_final` · `mlb_v3_stats.mlb_v3_stats_box_score_live_final` · `mlb_v3_stats.mlb_v3_stats_box_scores_by_date_final` · `mlb_v3_stats.mlb_v3_stats_box_scores_by_date_live_final` · `mlb_v3_stats.mlb_v3_stats_box_scores_delta_by_date` · `mlb_v3_stats.mlb_v3_stats_fantasy_points_by_date` · `mlb_v3_stats.mlb_v3_stats_player_game_logs_by_season` · `mlb_v3_stats.mlb_v3_stats_player_game_stats_by_date_final` · `mlb_v3_stats.mlb_v3_stats_player_game_stats_by_date_live_final` · `mlb_v3_stats.mlb_v3_stats_player_season_split_stats` · `mlb_v3_stats.mlb_v3_stats_player_season_stats` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_by_away` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_by_home` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_by_team` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_split_by_team` · `mlb_v3_stats.mlb_v3_stats_team_hitting_vs_starting_pitcher` · `mma_v3_odds.mma_v3_odds_betting_events_by_event` · `mma_v3_odds.mma_v3_odds_betting_events_upcoming` · `mma_v3_odds.mma_v3_odds_betting_fighter_props_by_event` · `mma_v3_odds.mma_v3_odds_betting_market` · `mma_v3_odds.mma_v3_odds_betting_markets_by_event` · `mma_v3_odds.mma_v3_odds_betting_markets_by_market_type` · `mma_v3_odds.mma_v3_odds_betting_metadata` · `mma_v3_odds.mma_v3_odds_event_fight_odds` · `mma_v3_odds.mma_v3_odds_event_fight_odds_line_movement` · `mma_v3_odds.mma_v3_odds_sportsbooks_by_active` · `mma_v3_scores.mma_v3_scores_event` · `mma_v3_scores.mma_v3_scores_fighter_profile_by_fighter` · `mma_v3_scores.mma_v3_scores_fighter_profiles_all` · `mma_v3_scores.mma_v3_scores_fighter_profiles_basic_all` · `mma_v3_scores.mma_v3_scores_leagues` · `mma_v3_scores.mma_v3_scores_schedules` · `mma_v3_stats.mma_v3_stats_fight_basic_final` · `mma_v3_stats.mma_v3_stats_fight_basic_live_final` · `mma_v3_stats.mma_v3_stats_fight_final` · `mma_v3_stats.mma_v3_stats_fight_live_final` · `nascar_v2.nascar_v2_driver_driver_profiles_by_driver` · `nascar_v2.nascar_v2_driverraceprojections_projected_player_race_stats_by_race` · `nascar_v2.nascar_v2_drivers_driver_profiles_all` · `nascar_v2.nascar_v2_driversbyactive_drivers_by_active` · `nascar_v2.nascar_v2_entrylistbyrace_entry_list_by_race` · `nascar_v2.nascar_v2_fantasygamestatsbyrace_fantasy_points_by_race` · `nascar_v2.nascar_v2_leaderboardbasic_leaderboard_basic` · `nascar_v2.nascar_v2_leaderboardbasicfinal_leaderboard_basic_final` · `nascar_v2.nascar_v2_raceresult_leaderboard_live_final` · `nascar_v2.nascar_v2_raceresultfinal_leaderboard_final` · `nascar_v2.nascar_v2_races_schedules` · `nascar_v2.nascar_v2_series_series` · `nascar_v2.nascar_v2_tracks_tracks` · `nascar_v3_odds.nascar_v3_odds_betting_events_by_date` · `nascar_v3_odds.nascar_v3_odds_betting_events_by_season` · `nascar_v3_odds.nascar_v3_odds_betting_market` · `nascar_v3_odds.nascar_v3_odds_betting_markets_by_event` · `nascar_v3_odds.nascar_v3_odds_betting_markets_by_market_type` · `nascar_v3_odds.nascar_v3_odds_betting_markets_by_race` · `nascar_v3_odds.nascar_v3_odds_betting_metadata` · `nascar_v3_odds.nascar_v3_odds_race_odds` · `nascar_v3_odds.nascar_v3_odds_race_odds_line_movement` · `nascar_v3_odds.nascar_v3_odds_sportsbooks_by_active` · `nba_v3_headshots.nba_v3_headshots_headshots` · `nba_v3_odds.nba_v3_odds_betting_events_by_date` · `nba_v3_odds.nba_v3_odds_betting_events_by_season` · `nba_v3_odds.nba_v3_odds_betting_futures_by_season` · `nba_v3_odds.nba_v3_odds_betting_futures_by_season_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_market` · `nba_v3_odds.nba_v3_odds_betting_market_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_markets_by_event` · `nba_v3_odds.nba_v3_odds_betting_markets_by_event_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_markets_by_game` · `nba_v3_odds.nba_v3_odds_betting_markets_by_game_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_markets_by_market_type` · `nba_v3_odds.nba_v3_odds_betting_markets_by_market_type_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_metadata` · `nba_v3_odds.nba_v3_odds_betting_player_props_by_game` · `nba_v3_odds.nba_v3_odds_betting_player_props_by_game_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_results_by_market` · `nba_v3_odds.nba_v3_odds_betting_results_by_market_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_splits_by_betting_market` · `nba_v3_odds.nba_v3_odds_betting_splits_by_game` · `nba_v3_odds.nba_v3_odds_betting_trends_by_matchup` · `nba_v3_odds.nba_v3_odds_betting_trends_by_team` · `nba_v3_odds.nba_v3_odds_in_game_odds_by_date` · `nba_v3_odds.nba_v3_odds_in_game_odds_by_date_sportsbook_group` · `nba_v3_odds.nba_v3_odds_in_game_odds_line_movement` · `nba_v3_odds.nba_v3_odds_in_game_odds_line_movement_sportsbook_group` · `nba_v3_odds.nba_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `nba_v3_odds.nba_v3_odds_period_game_odds_by_date` · `nba_v3_odds.nba_v3_odds_period_game_odds_line_movement` · `nba_v3_odds.nba_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `nba_v3_odds.nba_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `nba_v3_odds.nba_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `nba_v3_odds.nba_v3_odds_pre_game_odds_by_date` · `nba_v3_odds.nba_v3_odds_pre_game_odds_line_movement` · `nba_v3_odds.nba_v3_odds_sportsbooks_by_active` · `nba_v3_play_by_play.nba_v3_pbp_play_by_play_final` · `nba_v3_play_by_play.nba_v3_pbp_play_by_play_live_final` · `nba_v3_play_by_play.nba_v3_pbp_play_by_play_delta` · `nba_v3_projections.nba_v3_projections_dfs_slates_by_date` · `nba_v3_projections.nba_v3_projections_player_details_by_injured` · `nba_v3_projections.nba_v3_projections_projected_player_game_stats_by_date` · `nba_v3_projections.nba_v3_projections_projected_player_season_stats` · `nba_v3_projections.nba_v3_projections_projected_player_season_stats_by_team` · `nba_v3_projections.nba_v3_projections_starting_lineups_by_date` · `nba_v3_roto_baller_premium_news.nba_v3_news_rotoballer_premium_news` · `nba_v3_roto_baller_premium_news.nba_v3_news_rotoballer_premium_news_by_date` · `nba_v3_scores.nba_v3_scores_are_games_in_progress` · `nba_v3_scores.nba_v3_scores_depth_charts` · `nba_v3_scores.nba_v3_scores_games_by_date_final` · `nba_v3_scores.nba_v3_scores_games_by_date_live_final` · `nba_v3_scores.nba_v3_scores_games_basic_by_date_final` · `nba_v3_scores.nba_v3_scores_games_basic_by_date_live_final` · `nba_v3_scores.nba_v3_scores_news` · `nba_v3_scores.nba_v3_scores_news_by_date` · `nba_v3_scores.nba_v3_scores_player_details_by_active` · `nba_v3_scores.nba_v3_scores_player_details_by_free_agent` · `nba_v3_scores.nba_v3_scores_player_details_by_team` · `nba_v3_scores.nba_v3_scores_player_profiles_by_active` · `nba_v3_scores.nba_v3_scores_player_profiles_by_free_agent` · `nba_v3_scores.nba_v3_scores_players_profiles_by_team` · `nba_v3_scores.nba_v3_scores_referees` · `nba_v3_scores.nba_v3_scores_schedules` · `nba_v3_scores.nba_v3_scores_schedules_basic` · `nba_v3_scores.nba_v3_scores_season_current` · `nba_v3_scores.nba_v3_scores_stadiums` · `nba_v3_scores.nba_v3_scores_standings` · `nba_v3_scores.nba_v3_scores_team_game_logs_by_season` · `nba_v3_scores.nba_v3_scores_team_game_stats_by_date_live_final` · `nba_v3_scores.nba_v3_scores_team_profiles_all` · `nba_v3_scores.nba_v3_scores_team_profiles_by_active` · `nba_v3_scores.nba_v3_scores_team_profiles_by_season` · `nba_v3_scores.nba_v3_scores_team_season_stats` · `nba_v3_scores.nba_v3_scores_transactions` · `nba_v3_stats.nba_v3_stats_all_stars` · `nba_v3_stats.nba_v3_stats_box_score_final` · `nba_v3_stats.nba_v3_stats_box_score_live_final` · `nba_v3_stats.nba_v3_stats_box_scores_by_date_final` · `nba_v3_stats.nba_v3_stats_box_scores_by_date_live_final` · `nba_v3_stats.nba_v3_stats_box_scores_delta_by_date` · `nba_v3_stats.nba_v3_stats_fantasy_points_by_date` · `nba_v3_stats.nba_v3_stats_player_game_logs_by_season` · `nba_v3_stats.nba_v3_stats_player_game_stats_by_date_final` · `nba_v3_stats.nba_v3_stats_player_game_stats_by_date_live_final` · `nba_v3_stats.nba_v3_stats_player_season_stats` · `nba_v3_stats.nba_v3_stats_player_season_stats_by_team` · `nba_v3_stats.nba_v3_stats_team_game_stats_by_date_final` · `nba_v3_stats.nba_v3_stats_team_stats_allowed_by_position` · `nfl_v3_headshots.nfl_v3_headshots_headshots` · `nfl_v3_odds.nfl_v3_odds_betting_events_by_date` · `nfl_v3_odds.nfl_v3_odds_betting_events_by_season` · `nfl_v3_odds.nfl_v3_odds_betting_futures_by_season` · `nfl_v3_odds.nfl_v3_odds_betting_futures_by_season_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_market` · `nfl_v3_odds.nfl_v3_odds_betting_market_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_event` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_event_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_game` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_game_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_market_type` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_market_type_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_metadata` · `nfl_v3_odds.nfl_v3_odds_betting_player_props_by_game` · `nfl_v3_odds.nfl_v3_odds_betting_player_props_by_game_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_results_by_market` · `nfl_v3_odds.nfl_v3_odds_betting_results_by_market_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_splits_by_betting_market` · `nfl_v3_odds.nfl_v3_odds_betting_splits_by_game` · `nfl_v3_odds.nfl_v3_odds_betting_trends_by_matchup` · `nfl_v3_odds.nfl_v3_odds_betting_trends_by_team` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_by_week` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_by_week_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_line_movement` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_line_movement_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_period_game_odds_by_week` · `nfl_v3_odds.nfl_v3_odds_period_game_odds_line_movement` · `nfl_v3_odds.nfl_v3_odds_pre_game_and_period_game_odds_by_week_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_pre_game_odds_by_week` · `nfl_v3_odds.nfl_v3_odds_pre_game_odds_line_movement` · `nfl_v3_odds.nfl_v3_odds_sportsbooks_active` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_by_team_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_by_team_live_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_live_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_delta` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_simulation` · `nfl_v3_projections.nfl_v3_projections_dfs_slate_ownership_projections_by_slate` · `nfl_v3_projections.nfl_v3_projections_dfs_slate_ownership_projections_upcoming` · `nfl_v3_projections.nfl_v3_projections_dfs_slates_by_date` · `nfl_v3_projections.nfl_v3_projections_dfs_slates_by_week` · `nfl_v3_projections.nfl_v3_projections_idp_projected_player_game_stats_by_team` · `nfl_v3_projections.nfl_v3_projections_idp_projected_player_game_stats_by_week` · `nfl_v3_projections.nfl_v3_projections_player_details_by_injured` · `nfl_v3_projections.nfl_v3_projections_projected_fantasy_defense_game_stats_with_dfs_salaries` · `nfl_v3_projections.nfl_v3_projections_projected_fantasy_defense_season_stats_with_adp` · `nfl_v3_projections.nfl_v3_projections_projected_player_game_stats_by_team` · `nfl_v3_projections.nfl_v3_projections_projected_player_game_stats_by_week` · `nfl_v3_projections.nfl_v3_projections_projected_player_season_stats_with_adp` · `nfl_v3_projections.nfl_v3_projections_projected_player_season_stats_with_adp_by_team` · `nfl_v3_roto_baller_premium_news.nfl_v3_news_rotoballer_premium_news` · `nfl_v3_roto_baller_premium_news.nfl_v3_news_rotoballer_premium_news_by_date` · `nfl_v3_roto_baller_premium_news.nfl_v3_news_rotoballer_premium_news_by_team` · `nfl_v3_scores.nfl_v3_scores_are_games_in_progress` · `nfl_v3_scores.nfl_v3_scores_bye_weeks` · `nfl_v3_scores.nfl_v3_scores_depth_charts_all` · `nfl_v3_scores.nfl_v3_scores_depth_charts_by_active` · `nfl_v3_scores.nfl_v3_scores_depth_charts_by_week` · `nfl_v3_scores.nfl_v3_scores_games_by_date_final` · `nfl_v3_scores.nfl_v3_scores_games_by_date_live_final` · `nfl_v3_scores.nfl_v3_scores_games_by_season_live_final` · `nfl_v3_scores.nfl_v3_scores_games_by_week_final` · `nfl_v3_scores.nfl_v3_scores_games_by_week_live_final` · `nfl_v3_scores.nfl_v3_scores_games_basic_by_week_final` · `nfl_v3_scores.nfl_v3_scores_games_basic_by_week_live_final` · `nfl_v3_scores.nfl_v3_scores_news` · `nfl_v3_scores.nfl_v3_scores_news_by_date` · `nfl_v3_scores.nfl_v3_scores_news_by_team` · `nfl_v3_scores.nfl_v3_scores_player_details_all` · `nfl_v3_scores.nfl_v3_scores_player_details_by_free_agents` · `nfl_v3_scores.nfl_v3_scores_player_details_by_rookie_draft_year` · `nfl_v3_scores.nfl_v3_scores_player_details_by_team` · `nfl_v3_scores.nfl_v3_scores_player_game_logs_by_season` · `nfl_v3_scores.nfl_v3_scores_player_profiles_all` · `nfl_v3_scores.nfl_v3_scores_player_profiles_by_free_agent` · `nfl_v3_scores.nfl_v3_scores_player_profiles_by_rookie_draft_year` · `nfl_v3_scores.nfl_v3_scores_player_profiles_by_team` · `nfl_v3_scores.nfl_v3_scores_referees` · `nfl_v3_scores.nfl_v3_scores_schedules` · `nfl_v3_scores.nfl_v3_scores_schedules_basic` · `nfl_v3_scores.nfl_v3_scores_scores_by_week_simulation` · `nfl_v3_scores.nfl_v3_scores_season_current` · `nfl_v3_scores.nfl_v3_scores_season_last_completed` · `nfl_v3_scores.nfl_v3_scores_season_upcoming` · `nfl_v3_scores.nfl_v3_scores_stadiums` · `nfl_v3_scores.nfl_v3_scores_standings` · `nfl_v3_scores.nfl_v3_scores_team_game_stats_live_final` · `nfl_v3_scores.nfl_v3_scores_team_profiles_all` · `nfl_v3_scores.nfl_v3_scores_team_profiles_by_active` · `nfl_v3_scores.nfl_v3_scores_team_profiles_by_season` · `nfl_v3_scores.nfl_v3_scores_team_profiles_basic_all` · `nfl_v3_scores.nfl_v3_scores_team_season_stats` · `nfl_v3_scores.nfl_v3_scores_timeframes` · `nfl_v3_scores.nfl_v3_scores_transactions_by_date` · `nfl_v3_scores.nfl_v3_scores_week_current` · `nfl_v3_scores.nfl_v3_scores_week_last_completed` · `nfl_v3_scores.nfl_v3_scores_week_upcoming` · `nfl_v3_stats.nfl_v3_stats_box_score_by_team_final` · `nfl_v3_stats.nfl_v3_stats_box_score_by_team_live_final` · `nfl_v3_stats.nfl_v3_stats_box_score_final` · `nfl_v3_stats.nfl_v3_stats_box_score_live_final` · `nfl_v3_stats.nfl_v3_stats_box_scores_by_week_final` · `nfl_v3_stats.nfl_v3_stats_box_scores_delta_by_week` · `nfl_v3_stats.nfl_v3_stats_box_scores_simulation` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_game_stats_all` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_game_stats_by_team` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_season_stats_all` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_season_stats_by_team` · `nfl_v3_stats.nfl_v3_stats_fantasy_player_ownership_percentages_season_long_by_week` · `nfl_v3_stats.nfl_v3_stats_fantasy_points_by_week` · `nfl_v3_stats.nfl_v3_stats_games_by_season_final` · `nfl_v3_stats.nfl_v3_stats_injuries_all` · `nfl_v3_stats.nfl_v3_stats_injuries_by_team` · `nfl_v3_stats.nfl_v3_stats_player_game_logs_by_season` · `nfl_v3_stats.nfl_v3_stats_player_game_red_zone_stats` · `nfl_v3_stats.nfl_v3_stats_player_game_red_zone_stats_inside_five` · `nfl_v3_stats.nfl_v3_stats_player_game_red_zone_stats_inside_ten` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_team_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_team_live_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_week_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_week_live_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_delta` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_delta_by_week` · `nfl_v3_stats.nfl_v3_stats_player_season_red_zone_stats` · `nfl_v3_stats.nfl_v3_stats_player_season_red_zone_stats_inside_five` · `nfl_v3_stats.nfl_v3_stats_player_season_red_zone_stats_inside_ten` · `nfl_v3_stats.nfl_v3_stats_player_season_stats` · `nfl_v3_stats.nfl_v3_stats_player_season_stats_by_team` · `nfl_v3_stats.nfl_v3_stats_player_season_third_down_stats` · `nfl_v3_stats.nfl_v3_stats_pro_bowlers` · `nfl_v3_stats.nfl_v3_stats_team_game_stats_by_game_final` · `nhl_v3_headshots.nhl_v3_headshots_headshots` · `nhl_v3_odds.nhl_v3_odds_betting_events_by_date` · `nhl_v3_odds.nhl_v3_odds_betting_events_by_season` · `nhl_v3_odds.nhl_v3_odds_betting_futures_by_season` · `nhl_v3_odds.nhl_v3_odds_betting_futures_by_season_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_market` · `nhl_v3_odds.nhl_v3_odds_betting_market_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_event` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_event_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_game` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_game_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_market_type` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_market_type_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_metadata` · `nhl_v3_odds.nhl_v3_odds_betting_player_props_by_game` · `nhl_v3_odds.nhl_v3_odds_betting_player_props_by_game_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_results_by_market` · `nhl_v3_odds.nhl_v3_odds_betting_results_by_market_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_splits_by_betting_market` · `nhl_v3_odds.nhl_v3_odds_betting_splits_by_game` · `nhl_v3_odds.nhl_v3_odds_betting_trends_by_matchup` · `nhl_v3_odds.nhl_v3_odds_betting_trends_by_team` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_by_date` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_by_date_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_line_movement` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_line_movement_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_period_game_odds_by_date` · `nhl_v3_odds.nhl_v3_odds_period_game_odds_line_movement` · `nhl_v3_odds.nhl_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_pre_game_odds_by_date` · `nhl_v3_odds.nhl_v3_odds_pre_game_odds_line_movement` · `nhl_v3_odds.nhl_v3_odds_sportsbooks_by_active` · `nhl_v3_play_by_play.nhl_v3_pbp_play_by_play_final` · `nhl_v3_play_by_play.nhl_v3_pbp_play_by_play_live_final` · `nhl_v3_play_by_play.nhl_v3_pbp_play_by_play_delta` · `nhl_v3_projections.nhl_v3_projections_dfs_slates_by_date` · `nhl_v3_projections.nhl_v3_projections_player_details_by_injured` · `nhl_v3_projections.nhl_v3_projections_projected_player_game_stats_by_date` · `nhl_v3_projections.nhl_v3_projections_starting_goaltenders_by_date` · `nhl_v3_roto_baller_premium_news.nhl_v3_news_rotoballer_premium_news` · `nhl_v3_roto_baller_premium_news.nhl_v3_news_rotoballer_premium_news_by_date` · `nhl_v3_scores.nhl_v3_scores_are_games_in_progress` · `nhl_v3_scores.nhl_v3_scores_depth_charts_goalies` · `nhl_v3_scores.nhl_v3_scores_games_by_date_final` · `nhl_v3_scores.nhl_v3_scores_games_by_date_live_final` · `nhl_v3_scores.nhl_v3_scores_games_basic_by_date_final` · `nhl_v3_scores.nhl_v3_scores_games_basic_by_date_live_final` · `nhl_v3_scores.nhl_v3_scores_news` · `nhl_v3_scores.nhl_v3_scores_news_by_date` · `nhl_v3_scores.nhl_v3_scores_player_details_by_active` · `nhl_v3_scores.nhl_v3_scores_player_details_by_active2` · `nhl_v3_scores.nhl_v3_scores_player_details_by_free_agents` · `nhl_v3_scores.nhl_v3_scores_player_details_by_free_agents2` · `nhl_v3_scores.nhl_v3_scores_players_details_by_team` · `nhl_v3_scores.nhl_v3_scores_players_profiles_by_team` · `nhl_v3_scores.nhl_v3_scores_referees` · `nhl_v3_scores.nhl_v3_scores_schedules` · `nhl_v3_scores.nhl_v3_scores_schedules_basic` · `nhl_v3_scores.nhl_v3_scores_season_current` · `nhl_v3_scores.nhl_v3_scores_stadiums` · `nhl_v3_scores.nhl_v3_scores_standings` · `nhl_v3_scores.nhl_v3_scores_team_game_logs_by_season` · `nhl_v3_scores.nhl_v3_scores_team_game_stats_by_date_live_final` · `nhl_v3_scores.nhl_v3_scores_team_profiles_all` · `nhl_v3_scores.nhl_v3_scores_team_profiles_by_active` · `nhl_v3_scores.nhl_v3_scores_team_profiles_by_season` · `nhl_v3_scores.nhl_v3_scores_team_season_stats` · `nhl_v3_scores.nhl_v3_scores_transactions` · `nhl_v3_stats.nhl_v3_stats_box_score_final` · `nhl_v3_stats.nhl_v3_stats_box_score_live_final` · `nhl_v3_stats.nhl_v3_stats_box_scores_by_date_final` · `nhl_v3_stats.nhl_v3_stats_box_scores_by_date_live_final` · `nhl_v3_stats.nhl_v3_stats_box_scores_delta_by_date` · `nhl_v3_stats.nhl_v3_stats_fantasy_points_by_date` · `nhl_v3_stats.nhl_v3_stats_line_combinations_by_season` · `nhl_v3_stats.nhl_v3_stats_player_game_logs_by_season` · `nhl_v3_stats.nhl_v3_stats_player_game_stats_by_date_final` · `nhl_v3_stats.nhl_v3_stats_player_game_stats_by_date_live_final` · `nhl_v3_stats.nhl_v3_stats_player_season_stats` · `nhl_v3_stats.nhl_v3_stats_player_season_stats_by_team` · `nhl_v3_stats.nhl_v3_stats_team_game_stats_by_date_final` · `nhl_v3_stats.nhl_v3_stats_team_stats_allowed_by_position` · `soccer_v4_headshots.soccer_v4_headshots_headshots` · `soccer_v4_odds.soccer_v4_odds_betting_events_by_date` · `soccer_v4_odds.soccer_v4_odds_betting_events_by_season` · `soccer_v4_odds.soccer_v4_odds_betting_futures_by_season` · `soccer_v4_odds.soccer_v4_odds_betting_futures_by_season_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_market` · `soccer_v4_odds.soccer_v4_odds_betting_market_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_event` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_event_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_game` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_game_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_market_type` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_market_type_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_metadata` · `soccer_v4_odds.soccer_v4_odds_betting_player_props_by_game` · `soccer_v4_odds.soccer_v4_odds_betting_player_props_by_game_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_by_date` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_by_date_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_line_movement` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_line_movement_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_period_game_odds_by_date` · `soccer_v4_odds.soccer_v4_odds_period_game_odds_line_movement` · `soccer_v4_odds.soccer_v4_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_pre_game_odds_by_date` · `soccer_v4_odds.soccer_v4_odds_pre_game_odds_line_movement` · `soccer_v4_odds.soccer_v4_odds_sportsbooks_by_active` · `soccer_v4_projections.soccer_v4_projections_dfs_slates_by_date` · `soccer_v4_projections.soccer_v4_projections_player_details_by_injured` · `soccer_v4_projections.soccer_v4_projections_projected_player_game_stats_by_date` · `soccer_v4_projections.soccer_v4_projections_upcoming_dfs_slates_by_competition` · `soccer_v4_scores.soccer_v4_scores_areas_countries` · `soccer_v4_scores.soccer_v4_scores_competitions_leagues` · `soccer_v4_scores.soccer_v4_scores_games_by_competition_live_final` · `soccer_v4_scores.soccer_v4_scores_games_by_date_final` · `soccer_v4_scores.soccer_v4_scores_games_by_date_live_final` · `soccer_v4_scores.soccer_v4_scores_games_basic_by_date_final` · `soccer_v4_scores.soccer_v4_scores_games_basic_by_date_live_final` · `soccer_v4_scores.soccer_v4_scores_memberships_by_active` · `soccer_v4_scores.soccer_v4_scores_memberships_by_canceled` · `soccer_v4_scores.soccer_v4_scores_memberships_by_recently_changed` · `soccer_v4_scores.soccer_v4_scores_memberships_historical_by_team` · `soccer_v4_scores.soccer_v4_scores_player_details_by_team` · `soccer_v4_scores.soccer_v4_scores_player_profiles_by_team` · `soccer_v4_scores.soccer_v4_scores_schedules` · `soccer_v4_scores.soccer_v4_scores_schedules_basic` · `soccer_v4_scores.soccer_v4_scores_standings` · `soccer_v4_scores.soccer_v4_scores_team_game_stats_by_date_live_final` · `soccer_v4_scores.soccer_v4_scores_team_profiles_by_competition` · `soccer_v4_scores.soccer_v4_scores_team_profiles_by_season` · `soccer_v4_scores.soccer_v4_scores_team_season_stats` · `soccer_v4_scores.soccer_v4_scores_venues` · `soccer_v4_stats.soccer_v4_stats_box_score_final` · `soccer_v4_stats.soccer_v4_stats_box_score_live_final` · `soccer_v4_stats.soccer_v4_stats_box_scores_by_date_final` · `soccer_v4_stats.soccer_v4_stats_box_scores_by_date_live_final` · `soccer_v4_stats.soccer_v4_stats_box_scores_delta_by_date` · `soccer_v4_stats.soccer_v4_stats_fantasy_points_by_date` · `soccer_v4_stats.soccer_v4_stats_lineups_by_date` · `soccer_v4_stats.soccer_v4_stats_player_game_stats_by_date_final` · `soccer_v4_stats.soccer_v4_stats_player_game_stats_by_date_live_final` · `soccer_v4_stats.soccer_v4_stats_player_season_stats` · `soccer_v4_stats.soccer_v4_stats_team_game_stats_by_date_final` · `wnba_v3_scores.wnba_v3_scores_are_games_in_progress` · `wnba_v3_scores.wnba_v3_scores_betting_events_by_date` · `wnba_v3_scores.wnba_v3_scores_betting_events_by_season` · `wnba_v3_scores.wnba_v3_scores_betting_futures_by_season` · `wnba_v3_scores.wnba_v3_scores_betting_futures_by_season_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_market` · `wnba_v3_scores.wnba_v3_scores_betting_market_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_event` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_event_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_game` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_game_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_market_type` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_market_type_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_metadata` · `wnba_v3_scores.wnba_v3_scores_betting_player_props_by_game` · `wnba_v3_scores.wnba_v3_scores_betting_player_props_by_game_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_box_score_final` · `wnba_v3_scores.wnba_v3_scores_box_score_live_final` · `wnba_v3_scores.wnba_v3_scores_box_scores_by_date_final` · `wnba_v3_scores.wnba_v3_scores_box_scores_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_games_by_date_final` · `wnba_v3_scores.wnba_v3_scores_games_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_games_basic_by_date_final` · `wnba_v3_scores.wnba_v3_scores_games_basic_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_player_details_by_active` · `wnba_v3_scores.wnba_v3_scores_player_details_by_team` · `wnba_v3_scores.wnba_v3_scores_player_season_stats` · `wnba_v3_scores.wnba_v3_scores_player_season_stats_by_team` · `wnba_v3_scores.wnba_v3_scores_pre_game_odds_by_date` · `wnba_v3_scores.wnba_v3_scores_pre_game_odds_line_movement` · `wnba_v3_scores.wnba_v3_scores_schedules` · `wnba_v3_scores.wnba_v3_scores_schedules_basic` · `wnba_v3_scores.wnba_v3_scores_season_current` · `wnba_v3_scores.wnba_v3_scores_sportsbooks_by_active` · `wnba_v3_scores.wnba_v3_scores_stadiums` · `wnba_v3_scores.wnba_v3_scores_standings` · `wnba_v3_scores.wnba_v3_scores_team_game_stats_by_date_final` · `wnba_v3_scores.wnba_v3_scores_team_game_stats_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_team_profiles_all` · `wnba_v3_scores.wnba_v3_scores_team_season_stats`); distinguishing members *none required* - 2. So `isinstance(e.error, Unauthorized)` matches only 741 of 741 operations. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares no required member on any return type, so a truncated body decodes without complaint and the hole surfaces later.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which host the `base_url` selects**, because omitting it is silently the default. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "SportsDataIO Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any SportsDataIO Python SDK contract question or writing any SDK code." +--- + +# Getting started with the SportsDataIO Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing SportsDataIO Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-sportsdataio` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `sports_data_io/` and `pyproject.toml` of the generated package at version `1.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | SportsDataIO | +| Distribution name (what you install) | `sports-data-io` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `sports_data_io` — not the string you install | +| Source repository | https://github.com/context-plugins/sportsdata-python-sdk | +| Source branch | `main` | +| Version | `1.0` | +| Sync client class | `SportsDataIoClient` (alias `Client`) | +| Async client class | `AsyncSportsDataIoClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `api_key_header` · `api_key_query`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **API key** in the `Ocp-Apim-Subscription-Key` header — set `api_key_header` · **API key** in the `key` query parameter — set `api_key_query` | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.sportsdata.io` | +| Base-URL config | `ServerConfig` (`sports_data_io/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 741 operations across 50 controllers · 406 models · 0 unions · 5 enums · 741 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "sports-data-io @ git+https://github.com/context-plugins/sportsdata-python-sdk.git@main" +python -c "import sports_data_io, pathlib; print(pathlib.Path(sports_data_io.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from sports_data_io import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`sports_data_io/__init__.py` exports exactly 5 names beside the `sports_data_io.models` subpackage it re-exports: + +```python +from sports_data_io import ( + AsyncClient, + AsyncSportsDataIoClient, + Client, + ServerConfig, + SportsDataIoClient, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `sports_data_io.models` | +| Enums (and their open `…OrStr` aliases) | `sports_data_io.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `sports_data_io.core` | +| Per-operation error *unions* | `sports_data_io.errors` (`CbbV3OddsAlternateMarketPreGameOddsByDateErrorBody`, …) | + +`sports_data_io.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`sports_data_io/server/server_config.py`), and its default is **`https://api.sportsdata.io`**: + +```python +base_url: str = "https://api.sportsdata.io" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://api.sportsdata.io`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (two schemes) + +An API key sent as the `Ocp-Apim-Subscription-Key` header, exposed as the client's `api_key_header=` keyword taking a plain string. + +```python +from sports_data_io import Client + +client = Client(api_key_header="") +``` + +An API key sent as the `key` query parameter, exposed as the client's `api_key_query=` keyword taking a plain string. + +```python +from sports_data_io import Client + +client = Client(api_key_query="") +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.cbb_v3_odds` | `CbbV3Odds` / `AsyncCbbV3Odds` | 34 | `cbb_v3_odds_alternate_market_pre_game_odds_by_date` · `cbb_v3_odds_alternate_market_pre_game_odds_line_movement` · `cbb_v3_odds_betting_events_by_date` · `cbb_v3_odds_betting_events_by_season` · `cbb_v3_odds_betting_futures_by_season` · `cbb_v3_odds_betting_futures_by_season_sportsbook_group` · … | +| `client.cbb_v3_scores` | `CbbV3Scores` / `AsyncCbbV3Scores` | 22 | `cbb_v3_scores_are_games_in_progress` · `cbb_v3_scores_games_by_date_final` · `cbb_v3_scores_games_by_date_live_final` · `cbb_v3_scores_games_by_season` · `cbb_v3_scores_games_by_season_final` · `cbb_v3_scores_games_basic_by_date` · … | +| `client.cbb_v3_stats` | `CbbV3Stats` / `AsyncCbbV3Stats` | 11 | `cbb_v3_stats_box_score_final` · `cbb_v3_stats_box_score_live_final` · `cbb_v3_stats_box_scores_by_date_final` · `cbb_v3_stats_box_scores_by_date_live_final` · `cbb_v3_stats_box_scores_delta_by_date` · `cbb_v3_stats_player_game_logs_by_season` · … | +| `client.cfb_v3_odds` | `CfbV3Odds` / `AsyncCfbV3Odds` | 34 | `cfb_v3_odds_betting_events_by_date` · `cfb_v3_odds_betting_events_by_season` · `cfb_v3_odds_betting_futures_by_season` · `cfb_v3_odds_betting_futures_by_season_sportsbook_group` · `cfb_v3_odds_betting_market` · `cfb_v3_odds_betting_market_sportsbook_group` · … | +| `client.cfb_v3_scores` | `CfbV3Scores` / `AsyncCfbV3Scores` | 26 | `cfb_v3_scores_are_games_in_progress` · `cfb_v3_scores_conference_hierarchy` · `cfb_v3_scores_games_by_date_live_final` · `cfb_v3_scores_games_by_week_final` · `cfb_v3_scores_games_by_week_live_final` · `cfb_v3_scores_games_basic_by_date_live_final` · … | +| `client.cfb_v3_stats` | `CfbV3Stats` / `AsyncCfbV3Stats` | 13 | `cfb_v3_stats_box_score_final` · `cfb_v3_stats_box_score_live_final` · `cfb_v3_stats_box_scores_by_date_final` · `cfb_v3_stats_box_scores_by_date_live_final` · `cfb_v3_stats_box_scores_by_week_final` · `cfb_v3_stats_box_scores_by_week_live_final` · … | +| `client.cwbb_v3_scores` | `CwbbV3Scores` / `AsyncCwbbV3Scores` | 8 | `cwbb_v3_scores_are_any_games_in_progress` · `cwbb_v3_scores_games_by_date_final` · `cwbb_v3_scores_games_by_date_live_final` · `cwbb_v3_scores_league_hierarchy` · `cwbb_v3_scores_schedules` · `cwbb_v3_scores_schedules_basic` · … | +| `client.golf_v2` | `GolfV2` / `AsyncGolfV2` | 23 | `golf_v2_courses_courses` · `golf_v2_currentseason_season_current` · `golf_v2_dfsslateownershipprojectionsbyslateid_dfs_slate_ownership_projections_by_slate` · `golf_v2_dfsslatesbytournament_dfs_slates_by_tournament` · `golf_v2_fantasygamestatsbytournament_fantasy_points_by_tournament` · `golf_v2_leaderboard_leaderboard_live_final` · … | +| `client.golf_v3_headshots` | `GolfV3Headshots` / `AsyncGolfV3Headshots` | 1 | `golf_v3_headshots_headshots` | +| `client.golf_v3_odds` | `GolfV3Odds` / `AsyncGolfV3Odds` | 21 | `golf_v3_odds_betting_events_by_date` · `golf_v3_odds_betting_events_by_season` · `golf_v3_odds_betting_market` · `golf_v3_odds_betting_market_sportsbook_group` · `golf_v3_odds_betting_markets_by_event` · `golf_v3_odds_betting_markets_by_event_sportsbook_group` · … | +| `client.golf_v3_roto_baller_premium_news` | `GolfV3RotoBallerPremiumNews` / `AsyncGolfV3RotoBallerPremiumNews` | 2 | `golf_v3_news_rotoballer_premium_news` · `golf_v3_news_rotoballer_premium_news_by_date` | +| `client.mlb_v3_headshots` | `MlbV3Headshots` / `AsyncMlbV3Headshots` | 1 | `mlb_v3_headshots_headshots` | +| `client.mlb_v3_odds` | `MlbV3Odds` / `AsyncMlbV3Odds` | 34 | `mlb_v3_odds_betting_events_by_date` · `mlb_v3_odds_betting_events_by_season` · `mlb_v3_odds_betting_futures_by_season` · `mlb_v3_odds_betting_futures_by_season_sportsbook_group` · `mlb_v3_odds_betting_market` · `mlb_v3_odds_betting_market_sportsbook_group` · … | +| `client.mlb_v3_play_by_play` | `MlbV3PlayByPlay` / `AsyncMlbV3PlayByPlay` | 3 | `mlb_v3_pbp_play_by_play_final` · `mlb_v3_pbp_play_by_play_live_final` · `mlb_v3_pbp_play_by_play_delta` | +| `client.mlb_v3_projections` | `MlbV3Projections` / `AsyncMlbV3Projections` | 8 | `mlb_v3_projections_depth_charts` · `mlb_v3_projections_dfs_slate_ownership_projections_by_slate` · `mlb_v3_projections_dfs_slate_ownership_projections_upcoming` · `mlb_v3_projections_dfs_slates_by_date` · `mlb_v3_projections_player_details_by_injured` · `mlb_v3_projections_projected_player_game_stats_by_date` · … | +| `client.mlb_v3_roto_baller_premium_news` | `MlbV3RotoBallerPremiumNews` / `AsyncMlbV3RotoBallerPremiumNews` | 2 | `mlb_v3_news_rotoballer_premium_news` · `mlb_v3_news_rotoballer_premium_news_by_date` | +| `client.mlb_v3_scores` | `MlbV3Scores` / `AsyncMlbV3Scores` | 26 | `mlb_v3_scores_are_games_in_progress` · `mlb_v3_scores_games_by_date_final` · `mlb_v3_scores_games_by_date_live_final` · `mlb_v3_scores_games_basic_by_date_final` · `mlb_v3_scores_games_basic_by_date_live_final` · `mlb_v3_scores_news` · … | +| `client.mlb_v3_stats` | `MlbV3Stats` / `AsyncMlbV3Stats` | 17 | `mlb_v3_stats_batter_vs_pitcher_stats` · `mlb_v3_stats_box_score_final` · `mlb_v3_stats_box_score_live_final` · `mlb_v3_stats_box_scores_by_date_final` · `mlb_v3_stats_box_scores_by_date_live_final` · `mlb_v3_stats_box_scores_delta_by_date` · … | +| `client.mma_v3_odds` | `MmaV3Odds` / `AsyncMmaV3Odds` | 10 | `mma_v3_odds_betting_events_by_event` · `mma_v3_odds_betting_events_upcoming` · `mma_v3_odds_betting_fighter_props_by_event` · `mma_v3_odds_betting_market` · `mma_v3_odds_betting_markets_by_event` · `mma_v3_odds_betting_markets_by_market_type` · … | +| `client.mma_v3_scores` | `MmaV3Scores` / `AsyncMmaV3Scores` | 6 | `mma_v3_scores_event` · `mma_v3_scores_fighter_profile_by_fighter` · `mma_v3_scores_fighter_profiles_all` · `mma_v3_scores_fighter_profiles_basic_all` · `mma_v3_scores_leagues` · `mma_v3_scores_schedules` | +| `client.mma_v3_stats` | `MmaV3Stats` / `AsyncMmaV3Stats` | 4 | `mma_v3_stats_fight_basic_final` · `mma_v3_stats_fight_basic_live_final` · `mma_v3_stats_fight_final` · `mma_v3_stats_fight_live_final` | +| `client.nascar_v2` | `NascarV2` / `AsyncNascarV2` | 13 | `nascar_v2_driver_driver_profiles_by_driver` · `nascar_v2_driverraceprojections_projected_player_race_stats_by_race` · `nascar_v2_drivers_driver_profiles_all` · `nascar_v2_driversbyactive_drivers_by_active` · `nascar_v2_entrylistbyrace_entry_list_by_race` · `nascar_v2_fantasygamestatsbyrace_fantasy_points_by_race` · … | +| `client.nascar_v3_odds` | `NascarV3Odds` / `AsyncNascarV3Odds` | 10 | `nascar_v3_odds_betting_events_by_date` · `nascar_v3_odds_betting_events_by_season` · `nascar_v3_odds_betting_market` · `nascar_v3_odds_betting_markets_by_event` · `nascar_v3_odds_betting_markets_by_market_type` · `nascar_v3_odds_betting_markets_by_race` · … | +| `client.nba_v3_headshots` | `NbaV3Headshots` / `AsyncNbaV3Headshots` | 1 | `nba_v3_headshots_headshots` | +| `client.nba_v3_odds` | `NbaV3Odds` / `AsyncNbaV3Odds` | 34 | `nba_v3_odds_betting_events_by_date` · `nba_v3_odds_betting_events_by_season` · `nba_v3_odds_betting_futures_by_season` · `nba_v3_odds_betting_futures_by_season_sportsbook_group` · `nba_v3_odds_betting_market` · `nba_v3_odds_betting_market_sportsbook_group` · … | +| `client.nba_v3_play_by_play` | `NbaV3PlayByPlay` / `AsyncNbaV3PlayByPlay` | 3 | `nba_v3_pbp_play_by_play_final` · `nba_v3_pbp_play_by_play_live_final` · `nba_v3_pbp_play_by_play_delta` | +| `client.nba_v3_projections` | `NbaV3Projections` / `AsyncNbaV3Projections` | 6 | `nba_v3_projections_dfs_slates_by_date` · `nba_v3_projections_player_details_by_injured` · `nba_v3_projections_projected_player_game_stats_by_date` · `nba_v3_projections_projected_player_season_stats` · `nba_v3_projections_projected_player_season_stats_by_team` · `nba_v3_projections_starting_lineups_by_date` | +| `client.nba_v3_roto_baller_premium_news` | `NbaV3RotoBallerPremiumNews` / `AsyncNbaV3RotoBallerPremiumNews` | 2 | `nba_v3_news_rotoballer_premium_news` · `nba_v3_news_rotoballer_premium_news_by_date` | +| `client.nba_v3_scores` | `NbaV3Scores` / `AsyncNbaV3Scores` | 27 | `nba_v3_scores_are_games_in_progress` · `nba_v3_scores_depth_charts` · `nba_v3_scores_games_by_date_final` · `nba_v3_scores_games_by_date_live_final` · `nba_v3_scores_games_basic_by_date_final` · `nba_v3_scores_games_basic_by_date_live_final` · … | +| `client.nba_v3_stats` | `NbaV3Stats` / `AsyncNbaV3Stats` | 14 | `nba_v3_stats_all_stars` · `nba_v3_stats_box_score_final` · `nba_v3_stats_box_score_live_final` · `nba_v3_stats_box_scores_by_date_final` · `nba_v3_stats_box_scores_by_date_live_final` · `nba_v3_stats_box_scores_delta_by_date` · … | +| `client.nfl_v3_headshots` | `NflV3Headshots` / `AsyncNflV3Headshots` | 1 | `nfl_v3_headshots_headshots` | +| `client.nfl_v3_odds` | `NflV3Odds` / `AsyncNflV3Odds` | 34 | `nfl_v3_odds_betting_events_by_date` · `nfl_v3_odds_betting_events_by_season` · `nfl_v3_odds_betting_futures_by_season` · `nfl_v3_odds_betting_futures_by_season_sportsbook_group` · `nfl_v3_odds_betting_market` · `nfl_v3_odds_betting_market_sportsbook_group` · … | +| `client.nfl_v3_play_by_play` | `NflV3PlayByPlay` / `AsyncNflV3PlayByPlay` | 6 | `nfl_v3_pbp_play_by_play_by_team_final` · `nfl_v3_pbp_play_by_play_by_team_live_final` · `nfl_v3_pbp_play_by_play_final` · `nfl_v3_pbp_play_by_play_live_final` · `nfl_v3_pbp_play_by_play_delta` · `nfl_v3_pbp_play_by_play_simulation` | +| `client.nfl_v3_projections` | `NflV3Projections` / `AsyncNflV3Projections` | 13 | `nfl_v3_projections_dfs_slate_ownership_projections_by_slate` · `nfl_v3_projections_dfs_slate_ownership_projections_upcoming` · `nfl_v3_projections_dfs_slates_by_date` · `nfl_v3_projections_dfs_slates_by_week` · `nfl_v3_projections_idp_projected_player_game_stats_by_team` · `nfl_v3_projections_idp_projected_player_game_stats_by_week` · … | +| `client.nfl_v3_roto_baller_premium_news` | `NflV3RotoBallerPremiumNews` / `AsyncNflV3RotoBallerPremiumNews` | 3 | `nfl_v3_news_rotoballer_premium_news` · `nfl_v3_news_rotoballer_premium_news_by_date` · `nfl_v3_news_rotoballer_premium_news_by_team` | +| `client.nfl_v3_scores` | `NflV3Scores` / `AsyncNflV3Scores` | 44 | `nfl_v3_scores_are_games_in_progress` · `nfl_v3_scores_bye_weeks` · `nfl_v3_scores_depth_charts_all` · `nfl_v3_scores_depth_charts_by_active` · `nfl_v3_scores_depth_charts_by_week` · `nfl_v3_scores_games_by_date_final` · … | +| `client.nfl_v3_stats` | `NflV3Stats` / `AsyncNflV3Stats` | 34 | `nfl_v3_stats_box_score_by_team_final` · `nfl_v3_stats_box_score_by_team_live_final` · `nfl_v3_stats_box_score_final` · `nfl_v3_stats_box_score_live_final` · `nfl_v3_stats_box_scores_by_week_final` · `nfl_v3_stats_box_scores_delta_by_week` · … | +| `client.nhl_v3_headshots` | `NhlV3Headshots` / `AsyncNhlV3Headshots` | 1 | `nhl_v3_headshots_headshots` | +| `client.nhl_v3_odds` | `NhlV3Odds` / `AsyncNhlV3Odds` | 34 | `nhl_v3_odds_betting_events_by_date` · `nhl_v3_odds_betting_events_by_season` · `nhl_v3_odds_betting_futures_by_season` · `nhl_v3_odds_betting_futures_by_season_sportsbook_group` · `nhl_v3_odds_betting_market` · `nhl_v3_odds_betting_market_sportsbook_group` · … | +| `client.nhl_v3_play_by_play` | `NhlV3PlayByPlay` / `AsyncNhlV3PlayByPlay` | 3 | `nhl_v3_pbp_play_by_play_final` · `nhl_v3_pbp_play_by_play_live_final` · `nhl_v3_pbp_play_by_play_delta` | +| `client.nhl_v3_projections` | `NhlV3Projections` / `AsyncNhlV3Projections` | 4 | `nhl_v3_projections_dfs_slates_by_date` · `nhl_v3_projections_player_details_by_injured` · `nhl_v3_projections_projected_player_game_stats_by_date` · `nhl_v3_projections_starting_goaltenders_by_date` | +| `client.nhl_v3_roto_baller_premium_news` | `NhlV3RotoBallerPremiumNews` / `AsyncNhlV3RotoBallerPremiumNews` | 2 | `nhl_v3_news_rotoballer_premium_news` · `nhl_v3_news_rotoballer_premium_news_by_date` | +| `client.nhl_v3_scores` | `NhlV3Scores` / `AsyncNhlV3Scores` | 27 | `nhl_v3_scores_are_games_in_progress` · `nhl_v3_scores_depth_charts_goalies` · `nhl_v3_scores_games_by_date_final` · `nhl_v3_scores_games_by_date_live_final` · `nhl_v3_scores_games_basic_by_date_final` · `nhl_v3_scores_games_basic_by_date_live_final` · … | +| `client.nhl_v3_stats` | `NhlV3Stats` / `AsyncNhlV3Stats` | 14 | `nhl_v3_stats_box_score_final` · `nhl_v3_stats_box_score_live_final` · `nhl_v3_stats_box_scores_by_date_final` · `nhl_v3_stats_box_scores_by_date_live_final` · `nhl_v3_stats_box_scores_delta_by_date` · `nhl_v3_stats_fantasy_points_by_date` · … | +| `client.soccer_v4_headshots` | `SoccerV4Headshots` / `AsyncSoccerV4Headshots` | 1 | `soccer_v4_headshots_headshots` | +| `client.soccer_v4_odds` | `SoccerV4Odds` / `AsyncSoccerV4Odds` | 28 | `soccer_v4_odds_betting_events_by_date` · `soccer_v4_odds_betting_events_by_season` · `soccer_v4_odds_betting_futures_by_season` · `soccer_v4_odds_betting_futures_by_season_sportsbook_group` · `soccer_v4_odds_betting_market` · `soccer_v4_odds_betting_market_sportsbook_group` · … | +| `client.soccer_v4_projections` | `SoccerV4Projections` / `AsyncSoccerV4Projections` | 4 | `soccer_v4_projections_dfs_slates_by_date` · `soccer_v4_projections_player_details_by_injured` · `soccer_v4_projections_projected_player_game_stats_by_date` · `soccer_v4_projections_upcoming_dfs_slates_by_competition` | +| `client.soccer_v4_scores` | `SoccerV4Scores` / `AsyncSoccerV4Scores` | 21 | `soccer_v4_scores_areas_countries` · `soccer_v4_scores_competitions_leagues` · `soccer_v4_scores_games_by_competition_live_final` · `soccer_v4_scores_games_by_date_final` · `soccer_v4_scores_games_by_date_live_final` · `soccer_v4_scores_games_basic_by_date_final` · … | +| `client.soccer_v4_stats` | `SoccerV4Stats` / `AsyncSoccerV4Stats` | 11 | `soccer_v4_stats_box_score_final` · `soccer_v4_stats_box_score_live_final` · `soccer_v4_stats_box_scores_by_date_final` · `soccer_v4_stats_box_scores_by_date_live_final` · `soccer_v4_stats_box_scores_delta_by_date` · `soccer_v4_stats_fantasy_points_by_date` · … | +| `client.wnba_v3_scores` | `WnbaV3Scores` / `AsyncWnbaV3Scores` | 40 | `wnba_v3_scores_are_games_in_progress` · `wnba_v3_scores_betting_events_by_date` · `wnba_v3_scores_betting_events_by_season` · `wnba_v3_scores_betting_futures_by_season` · `wnba_v3_scores_betting_futures_by_season_sportsbook_group` · `wnba_v3_scores_betting_market` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `sports_data_io/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `sports_data_io/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/sportsdata-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `sports_data_io/models/area.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `sports_data_io/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import sports_data_io, pathlib; print(pathlib.Path(sports_data_io.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/sports_data_io` on Windows, `.venv/lib/python3.*/site-packages/sports_data_io` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The two schemes are `api_key_header=` and `api_key_query=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **1 operation returns `None`**, so `with_raw_response` is the only way to observe its status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 1 operation that returns `None`** — `wnba_v3_scores.wnba_v3_scores_standings`. Its raw peer is `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — there is **one** typed error body in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `Unauthorized` — 741 operations (`cbb_v3_odds.cbb_v3_odds_alternate_market_pre_game_odds_by_date` · `cbb_v3_odds.cbb_v3_odds_alternate_market_pre_game_odds_line_movement` · `cbb_v3_odds.cbb_v3_odds_betting_events_by_date` · `cbb_v3_odds.cbb_v3_odds_betting_events_by_season` · `cbb_v3_odds.cbb_v3_odds_betting_futures_by_season` · `cbb_v3_odds.cbb_v3_odds_betting_futures_by_season_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_market` · `cbb_v3_odds.cbb_v3_odds_betting_market_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_event` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_event_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_game` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_game_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_market_type` · `cbb_v3_odds.cbb_v3_odds_betting_markets_by_market_type_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_metadata` · `cbb_v3_odds.cbb_v3_odds_betting_player_props_by_game` · `cbb_v3_odds.cbb_v3_odds_betting_player_props_by_game_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_results_by_market` · `cbb_v3_odds.cbb_v3_odds_betting_results_by_market_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_betting_splits_by_betting_market` · `cbb_v3_odds.cbb_v3_odds_betting_splits_by_game` · `cbb_v3_odds.cbb_v3_odds_betting_trends_by_matchup` · `cbb_v3_odds.cbb_v3_odds_betting_trends_by_team` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_by_date` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_by_date_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_line_movement` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_line_movement_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `cbb_v3_odds.cbb_v3_odds_pre_game_odds_by_date` · `cbb_v3_odds.cbb_v3_odds_pre_game_odds_line_movement` · `cbb_v3_odds.cbb_v3_odds_sportsbooks_by_active` · `cbb_v3_scores.cbb_v3_scores_are_games_in_progress` · `cbb_v3_scores.cbb_v3_scores_games_by_date_final` · `cbb_v3_scores.cbb_v3_scores_games_by_date_live_final` · `cbb_v3_scores.cbb_v3_scores_games_by_season` · `cbb_v3_scores.cbb_v3_scores_games_by_season_final` · `cbb_v3_scores.cbb_v3_scores_games_basic_by_date` · `cbb_v3_scores.cbb_v3_scores_league_hierarchy` · `cbb_v3_scores.cbb_v3_scores_player_details_by_active` · `cbb_v3_scores.cbb_v3_scores_player_details_by_active2` · `cbb_v3_scores.cbb_v3_scores_player_details_by_injured` · `cbb_v3_scores.cbb_v3_scores_player_details_by_team` · `cbb_v3_scores.cbb_v3_scores_player_profiles_by_team` · `cbb_v3_scores.cbb_v3_scores_schedules_by_team` · `cbb_v3_scores.cbb_v3_scores_schedules_basic` · `cbb_v3_scores.cbb_v3_scores_season_current` · `cbb_v3_scores.cbb_v3_scores_stadiums` · `cbb_v3_scores.cbb_v3_scores_team_game_logs_by_season` · `cbb_v3_scores.cbb_v3_scores_team_game_stats_by_date_live_final` · `cbb_v3_scores.cbb_v3_scores_team_profiles_all` · `cbb_v3_scores.cbb_v3_scores_team_season_stats` · `cbb_v3_scores.cbb_v3_scores_teams_basic_all` · `cbb_v3_scores.cbb_v3_scores_tournament_hierarchy` · `cbb_v3_stats.cbb_v3_stats_box_score_final` · `cbb_v3_stats.cbb_v3_stats_box_score_live_final` · `cbb_v3_stats.cbb_v3_stats_box_scores_by_date_final` · `cbb_v3_stats.cbb_v3_stats_box_scores_by_date_live_final` · `cbb_v3_stats.cbb_v3_stats_box_scores_delta_by_date` · `cbb_v3_stats.cbb_v3_stats_player_game_logs_by_season` · `cbb_v3_stats.cbb_v3_stats_player_game_stats_by_date_final` · `cbb_v3_stats.cbb_v3_stats_player_game_stats_by_date_live_final` · `cbb_v3_stats.cbb_v3_stats_player_season_stats` · `cbb_v3_stats.cbb_v3_stats_player_season_stats_by_team` · `cbb_v3_stats.cbb_v3_stats_team_game_stats_by_date_final` · `cfb_v3_odds.cfb_v3_odds_betting_events_by_date` · `cfb_v3_odds.cfb_v3_odds_betting_events_by_season` · `cfb_v3_odds.cfb_v3_odds_betting_futures_by_season` · `cfb_v3_odds.cfb_v3_odds_betting_futures_by_season_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_market` · `cfb_v3_odds.cfb_v3_odds_betting_market_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_event` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_event_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_game` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_game_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_market_type` · `cfb_v3_odds.cfb_v3_odds_betting_markets_by_market_type_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_metadata` · `cfb_v3_odds.cfb_v3_odds_betting_player_props_by_game` · `cfb_v3_odds.cfb_v3_odds_betting_player_props_by_game_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_results_by_market` · `cfb_v3_odds.cfb_v3_odds_betting_results_by_market_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_betting_splits_by_betting_market` · `cfb_v3_odds.cfb_v3_odds_betting_splits_by_game` · `cfb_v3_odds.cfb_v3_odds_betting_trends_by_matchup` · `cfb_v3_odds.cfb_v3_odds_betting_trends_by_team` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_by_week` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_by_week_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_line_movement` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_line_movement_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_period_game_odds_by_week` · `cfb_v3_odds.cfb_v3_odds_period_game_odds_line_movement` · `cfb_v3_odds.cfb_v3_odds_pre_game_and_period_game_odds_by_week_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `cfb_v3_odds.cfb_v3_odds_pre_game_odds_by_week` · `cfb_v3_odds.cfb_v3_odds_pre_game_odds_line_movement` · `cfb_v3_odds.cfb_v3_odds_sportsbooks_by_active` · `cfb_v3_scores.cfb_v3_scores_are_games_in_progress` · `cfb_v3_scores.cfb_v3_scores_conference_hierarchy` · `cfb_v3_scores.cfb_v3_scores_games_by_date_live_final` · `cfb_v3_scores.cfb_v3_scores_games_by_week_final` · `cfb_v3_scores.cfb_v3_scores_games_by_week_live_final` · `cfb_v3_scores.cfb_v3_scores_games_basic_by_date_live_final` · `cfb_v3_scores.cfb_v3_scores_games_basic_by_week_final` · `cfb_v3_scores.cfb_v3_scores_games_basic_by_date_final` · `cfb_v3_scores.cfb_v3_scores_games_by_date_final` · `cfb_v3_scores.cfb_v3_scores_player_details_by_active` · `cfb_v3_scores.cfb_v3_scores_player_details_by_active2` · `cfb_v3_scores.cfb_v3_scores_player_details_by_injured` · `cfb_v3_scores.cfb_v3_scores_player_details_by_team` · `cfb_v3_scores.cfb_v3_scores_player_profiles_by_team` · `cfb_v3_scores.cfb_v3_scores_schedules` · `cfb_v3_scores.cfb_v3_scores_schedules_basic` · `cfb_v3_scores.cfb_v3_scores_season_current` · `cfb_v3_scores.cfb_v3_scores_season_current_details` · `cfb_v3_scores.cfb_v3_scores_season_type_current` · `cfb_v3_scores.cfb_v3_scores_stadiums` · `cfb_v3_scores.cfb_v3_scores_team_game_logs_by_season` · `cfb_v3_scores.cfb_v3_scores_team_game_stats_by_week_live_final` · `cfb_v3_scores.cfb_v3_scores_team_profiles_all` · `cfb_v3_scores.cfb_v3_scores_team_season_stats_standings` · `cfb_v3_scores.cfb_v3_scores_teams_basic_all` · `cfb_v3_scores.cfb_v3_scores_week_current` · `cfb_v3_stats.cfb_v3_stats_box_score_final` · `cfb_v3_stats.cfb_v3_stats_box_score_live_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_date_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_date_live_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_week_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_by_week_live_final` · `cfb_v3_stats.cfb_v3_stats_box_scores_delta_by_week` · `cfb_v3_stats.cfb_v3_stats_player_game_logs_by_season` · `cfb_v3_stats.cfb_v3_stats_player_game_stats_by_week_final` · `cfb_v3_stats.cfb_v3_stats_player_game_stats_by_week_live_final` · `cfb_v3_stats.cfb_v3_stats_player_season_stats` · `cfb_v3_stats.cfb_v3_stats_player_season_stats_by_team` · `cfb_v3_stats.cfb_v3_stats_team_game_stats_by_week_final` · `cwbb_v3_scores.cwbb_v3_scores_are_any_games_in_progress` · `cwbb_v3_scores.cwbb_v3_scores_games_by_date_final` · `cwbb_v3_scores.cwbb_v3_scores_games_by_date_live_final` · `cwbb_v3_scores.cwbb_v3_scores_league_hierarchy` · `cwbb_v3_scores.cwbb_v3_scores_schedules` · `cwbb_v3_scores.cwbb_v3_scores_schedules_basic` · `cwbb_v3_scores.cwbb_v3_scores_season_current` · `cwbb_v3_scores.cwbb_v3_scores_team_profiles_all` · `golf_v2.golf_v2_courses_courses` · `golf_v2.golf_v2_currentseason_season_current` · `golf_v2.golf_v2_dfsslateownershipprojectionsbyslateid_dfs_slate_ownership_projections_by_slate` · `golf_v2.golf_v2_dfsslatesbytournament_dfs_slates_by_tournament` · `golf_v2.golf_v2_fantasygamestatsbytournament_fantasy_points_by_tournament` · `golf_v2.golf_v2_leaderboard_leaderboard_live_final` · `golf_v2.golf_v2_leaderboardbasic_leaderboard_basic` · `golf_v2.golf_v2_leaderboardbasicfinal_leaderboard_basic_final` · `golf_v2.golf_v2_leaderboardfinal_leaderboard_final` · `golf_v2.golf_v2_news_news` · `golf_v2.golf_v2_newsbydate_news_by_date` · `golf_v2.golf_v2_player_player` · `golf_v2.golf_v2_players_player_profiles_all` · `golf_v2.golf_v2_playerseasonstats_player_season_stats` · `golf_v2.golf_v2_playertournamentholescores_player_hole_scores_by_tournament_live_final` · `golf_v2.golf_v2_playertournamentholescoresfinal_player_hole_scores_by_tournament_final` · `golf_v2.golf_v2_playertournamentprojectionstats_player_projected_stats_by_tournament` · `golf_v2.golf_v2_playertournamentroundscores_scores_by_tournament_live_final` · `golf_v2.golf_v2_playertournamentroundscoresfinal_scores_by_tournament_final` · `golf_v2.golf_v2_rankings_rankings` · `golf_v2.golf_v2_tournaments_schedule_by_season` · `golf_v2.golf_v2_tournaments_schedules` · `golf_v2.golf_v2_upcomingdfsslateownershipprojections_dfs_slate_ownership_projections_upcoming` · `golf_v3_headshots.golf_v3_headshots_headshots` · `golf_v3_odds.golf_v3_odds_betting_events_by_date` · `golf_v3_odds.golf_v3_odds_betting_events_by_season` · `golf_v3_odds.golf_v3_odds_betting_market` · `golf_v3_odds.golf_v3_odds_betting_market_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_markets_by_event` · `golf_v3_odds.golf_v3_odds_betting_markets_by_event_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_markets_by_market_type` · `golf_v3_odds.golf_v3_odds_betting_markets_by_market_type_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_markets_by_tournament` · `golf_v3_odds.golf_v3_odds_betting_markets_by_tournament_sportsbook_group` · `golf_v3_odds.golf_v3_odds_betting_metadata` · `golf_v3_odds.golf_v3_odds_betting_player_props_by_tournament_sportsbook_group` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds_sportsbook_group` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds_line_movement` · `golf_v3_odds.golf_v3_odds_in_play_tournament_odds_line_movement_sportsbook_group` · `golf_v3_odds.golf_v3_odds_sportsbooks_by_active` · `golf_v3_odds.golf_v3_odds_tournament_odds` · `golf_v3_odds.golf_v3_odds_tournament_odds_sportsbook_group` · `golf_v3_odds.golf_v3_odds_tournament_odds_line_movement` · `golf_v3_odds.golf_v3_odds_tournament_odds_line_movement_sportsbook_group` · `golf_v3_roto_baller_premium_news.golf_v3_news_rotoballer_premium_news` · `golf_v3_roto_baller_premium_news.golf_v3_news_rotoballer_premium_news_by_date` · `mlb_v3_headshots.mlb_v3_headshots_headshots` · `mlb_v3_odds.mlb_v3_odds_betting_events_by_date` · `mlb_v3_odds.mlb_v3_odds_betting_events_by_season` · `mlb_v3_odds.mlb_v3_odds_betting_futures_by_season` · `mlb_v3_odds.mlb_v3_odds_betting_futures_by_season_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_market` · `mlb_v3_odds.mlb_v3_odds_betting_market_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_event` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_event_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_game` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_game_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_market_type` · `mlb_v3_odds.mlb_v3_odds_betting_markets_by_market_type_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_metadata` · `mlb_v3_odds.mlb_v3_odds_betting_player_props_by_game` · `mlb_v3_odds.mlb_v3_odds_betting_player_props_by_game_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_results_by_market` · `mlb_v3_odds.mlb_v3_odds_betting_results_by_market_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_betting_splits_by_game` · `mlb_v3_odds.mlb_v3_odds_betting_splits_by_betting_market` · `mlb_v3_odds.mlb_v3_odds_betting_trends_by_matchup` · `mlb_v3_odds.mlb_v3_odds_betting_trends_by_team` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_by_date` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_by_date_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_line_movement` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_line_movement_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_period_game_odds_by_date` · `mlb_v3_odds.mlb_v3_odds_period_game_odds_line_movement` · `mlb_v3_odds.mlb_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `mlb_v3_odds.mlb_v3_odds_pre_game_odds_by_date` · `mlb_v3_odds.mlb_v3_odds_pre_game_odds_line_movement` · `mlb_v3_odds.mlb_v3_odds_sportsbooks_by_active` · `mlb_v3_play_by_play.mlb_v3_pbp_play_by_play_final` · `mlb_v3_play_by_play.mlb_v3_pbp_play_by_play_live_final` · `mlb_v3_play_by_play.mlb_v3_pbp_play_by_play_delta` · `mlb_v3_projections.mlb_v3_projections_depth_charts` · `mlb_v3_projections.mlb_v3_projections_dfs_slate_ownership_projections_by_slate` · `mlb_v3_projections.mlb_v3_projections_dfs_slate_ownership_projections_upcoming` · `mlb_v3_projections.mlb_v3_projections_dfs_slates_by_date` · `mlb_v3_projections.mlb_v3_projections_player_details_by_injured` · `mlb_v3_projections.mlb_v3_projections_projected_player_game_stats_by_date` · `mlb_v3_projections.mlb_v3_projections_projected_player_season_stats_with_adp` · `mlb_v3_projections.mlb_v3_projections_starting_lineups_by_date` · `mlb_v3_roto_baller_premium_news.mlb_v3_news_rotoballer_premium_news` · `mlb_v3_roto_baller_premium_news.mlb_v3_news_rotoballer_premium_news_by_date` · `mlb_v3_scores.mlb_v3_scores_are_games_in_progress` · `mlb_v3_scores.mlb_v3_scores_games_by_date_final` · `mlb_v3_scores.mlb_v3_scores_games_by_date_live_final` · `mlb_v3_scores.mlb_v3_scores_games_basic_by_date_final` · `mlb_v3_scores.mlb_v3_scores_games_basic_by_date_live_final` · `mlb_v3_scores.mlb_v3_scores_news` · `mlb_v3_scores.mlb_v3_scores_news_by_date` · `mlb_v3_scores.mlb_v3_scores_player_details_by_active` · `mlb_v3_scores.mlb_v3_scores_player_details_by_free_agents` · `mlb_v3_scores.mlb_v3_scores_player_profiles_by_active` · `mlb_v3_scores.mlb_v3_scores_player_profiles_by_free_agents` · `mlb_v3_scores.mlb_v3_scores_player_profiles_by_team` · `mlb_v3_scores.mlb_v3_scores_players_details_by_team` · `mlb_v3_scores.mlb_v3_scores_schedules` · `mlb_v3_scores.mlb_v3_scores_schedules_basic` · `mlb_v3_scores.mlb_v3_scores_season_current` · `mlb_v3_scores.mlb_v3_scores_stadiums` · `mlb_v3_scores.mlb_v3_scores_standings` · `mlb_v3_scores.mlb_v3_scores_team_game_logs_by_season` · `mlb_v3_scores.mlb_v3_scores_team_game_stats_by_date_final` · `mlb_v3_scores.mlb_v3_scores_team_game_stats_by_date_live_final` · `mlb_v3_scores.mlb_v3_scores_team_profiles_all` · `mlb_v3_scores.mlb_v3_scores_team_profiles_by_active` · `mlb_v3_scores.mlb_v3_scores_team_profiles_by_season` · `mlb_v3_scores.mlb_v3_scores_team_season_stats` · `mlb_v3_scores.mlb_v3_scores_transactions` · `mlb_v3_stats.mlb_v3_stats_batter_vs_pitcher_stats` · `mlb_v3_stats.mlb_v3_stats_box_score_final` · `mlb_v3_stats.mlb_v3_stats_box_score_live_final` · `mlb_v3_stats.mlb_v3_stats_box_scores_by_date_final` · `mlb_v3_stats.mlb_v3_stats_box_scores_by_date_live_final` · `mlb_v3_stats.mlb_v3_stats_box_scores_delta_by_date` · `mlb_v3_stats.mlb_v3_stats_fantasy_points_by_date` · `mlb_v3_stats.mlb_v3_stats_player_game_logs_by_season` · `mlb_v3_stats.mlb_v3_stats_player_game_stats_by_date_final` · `mlb_v3_stats.mlb_v3_stats_player_game_stats_by_date_live_final` · `mlb_v3_stats.mlb_v3_stats_player_season_split_stats` · `mlb_v3_stats.mlb_v3_stats_player_season_stats` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_by_away` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_by_home` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_by_team` · `mlb_v3_stats.mlb_v3_stats_player_season_stats_split_by_team` · `mlb_v3_stats.mlb_v3_stats_team_hitting_vs_starting_pitcher` · `mma_v3_odds.mma_v3_odds_betting_events_by_event` · `mma_v3_odds.mma_v3_odds_betting_events_upcoming` · `mma_v3_odds.mma_v3_odds_betting_fighter_props_by_event` · `mma_v3_odds.mma_v3_odds_betting_market` · `mma_v3_odds.mma_v3_odds_betting_markets_by_event` · `mma_v3_odds.mma_v3_odds_betting_markets_by_market_type` · `mma_v3_odds.mma_v3_odds_betting_metadata` · `mma_v3_odds.mma_v3_odds_event_fight_odds` · `mma_v3_odds.mma_v3_odds_event_fight_odds_line_movement` · `mma_v3_odds.mma_v3_odds_sportsbooks_by_active` · `mma_v3_scores.mma_v3_scores_event` · `mma_v3_scores.mma_v3_scores_fighter_profile_by_fighter` · `mma_v3_scores.mma_v3_scores_fighter_profiles_all` · `mma_v3_scores.mma_v3_scores_fighter_profiles_basic_all` · `mma_v3_scores.mma_v3_scores_leagues` · `mma_v3_scores.mma_v3_scores_schedules` · `mma_v3_stats.mma_v3_stats_fight_basic_final` · `mma_v3_stats.mma_v3_stats_fight_basic_live_final` · `mma_v3_stats.mma_v3_stats_fight_final` · `mma_v3_stats.mma_v3_stats_fight_live_final` · `nascar_v2.nascar_v2_driver_driver_profiles_by_driver` · `nascar_v2.nascar_v2_driverraceprojections_projected_player_race_stats_by_race` · `nascar_v2.nascar_v2_drivers_driver_profiles_all` · `nascar_v2.nascar_v2_driversbyactive_drivers_by_active` · `nascar_v2.nascar_v2_entrylistbyrace_entry_list_by_race` · `nascar_v2.nascar_v2_fantasygamestatsbyrace_fantasy_points_by_race` · `nascar_v2.nascar_v2_leaderboardbasic_leaderboard_basic` · `nascar_v2.nascar_v2_leaderboardbasicfinal_leaderboard_basic_final` · `nascar_v2.nascar_v2_raceresult_leaderboard_live_final` · `nascar_v2.nascar_v2_raceresultfinal_leaderboard_final` · `nascar_v2.nascar_v2_races_schedules` · `nascar_v2.nascar_v2_series_series` · `nascar_v2.nascar_v2_tracks_tracks` · `nascar_v3_odds.nascar_v3_odds_betting_events_by_date` · `nascar_v3_odds.nascar_v3_odds_betting_events_by_season` · `nascar_v3_odds.nascar_v3_odds_betting_market` · `nascar_v3_odds.nascar_v3_odds_betting_markets_by_event` · `nascar_v3_odds.nascar_v3_odds_betting_markets_by_market_type` · `nascar_v3_odds.nascar_v3_odds_betting_markets_by_race` · `nascar_v3_odds.nascar_v3_odds_betting_metadata` · `nascar_v3_odds.nascar_v3_odds_race_odds` · `nascar_v3_odds.nascar_v3_odds_race_odds_line_movement` · `nascar_v3_odds.nascar_v3_odds_sportsbooks_by_active` · `nba_v3_headshots.nba_v3_headshots_headshots` · `nba_v3_odds.nba_v3_odds_betting_events_by_date` · `nba_v3_odds.nba_v3_odds_betting_events_by_season` · `nba_v3_odds.nba_v3_odds_betting_futures_by_season` · `nba_v3_odds.nba_v3_odds_betting_futures_by_season_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_market` · `nba_v3_odds.nba_v3_odds_betting_market_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_markets_by_event` · `nba_v3_odds.nba_v3_odds_betting_markets_by_event_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_markets_by_game` · `nba_v3_odds.nba_v3_odds_betting_markets_by_game_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_markets_by_market_type` · `nba_v3_odds.nba_v3_odds_betting_markets_by_market_type_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_metadata` · `nba_v3_odds.nba_v3_odds_betting_player_props_by_game` · `nba_v3_odds.nba_v3_odds_betting_player_props_by_game_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_results_by_market` · `nba_v3_odds.nba_v3_odds_betting_results_by_market_sportsbook_group` · `nba_v3_odds.nba_v3_odds_betting_splits_by_betting_market` · `nba_v3_odds.nba_v3_odds_betting_splits_by_game` · `nba_v3_odds.nba_v3_odds_betting_trends_by_matchup` · `nba_v3_odds.nba_v3_odds_betting_trends_by_team` · `nba_v3_odds.nba_v3_odds_in_game_odds_by_date` · `nba_v3_odds.nba_v3_odds_in_game_odds_by_date_sportsbook_group` · `nba_v3_odds.nba_v3_odds_in_game_odds_line_movement` · `nba_v3_odds.nba_v3_odds_in_game_odds_line_movement_sportsbook_group` · `nba_v3_odds.nba_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `nba_v3_odds.nba_v3_odds_period_game_odds_by_date` · `nba_v3_odds.nba_v3_odds_period_game_odds_line_movement` · `nba_v3_odds.nba_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `nba_v3_odds.nba_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `nba_v3_odds.nba_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `nba_v3_odds.nba_v3_odds_pre_game_odds_by_date` · `nba_v3_odds.nba_v3_odds_pre_game_odds_line_movement` · `nba_v3_odds.nba_v3_odds_sportsbooks_by_active` · `nba_v3_play_by_play.nba_v3_pbp_play_by_play_final` · `nba_v3_play_by_play.nba_v3_pbp_play_by_play_live_final` · `nba_v3_play_by_play.nba_v3_pbp_play_by_play_delta` · `nba_v3_projections.nba_v3_projections_dfs_slates_by_date` · `nba_v3_projections.nba_v3_projections_player_details_by_injured` · `nba_v3_projections.nba_v3_projections_projected_player_game_stats_by_date` · `nba_v3_projections.nba_v3_projections_projected_player_season_stats` · `nba_v3_projections.nba_v3_projections_projected_player_season_stats_by_team` · `nba_v3_projections.nba_v3_projections_starting_lineups_by_date` · `nba_v3_roto_baller_premium_news.nba_v3_news_rotoballer_premium_news` · `nba_v3_roto_baller_premium_news.nba_v3_news_rotoballer_premium_news_by_date` · `nba_v3_scores.nba_v3_scores_are_games_in_progress` · `nba_v3_scores.nba_v3_scores_depth_charts` · `nba_v3_scores.nba_v3_scores_games_by_date_final` · `nba_v3_scores.nba_v3_scores_games_by_date_live_final` · `nba_v3_scores.nba_v3_scores_games_basic_by_date_final` · `nba_v3_scores.nba_v3_scores_games_basic_by_date_live_final` · `nba_v3_scores.nba_v3_scores_news` · `nba_v3_scores.nba_v3_scores_news_by_date` · `nba_v3_scores.nba_v3_scores_player_details_by_active` · `nba_v3_scores.nba_v3_scores_player_details_by_free_agent` · `nba_v3_scores.nba_v3_scores_player_details_by_team` · `nba_v3_scores.nba_v3_scores_player_profiles_by_active` · `nba_v3_scores.nba_v3_scores_player_profiles_by_free_agent` · `nba_v3_scores.nba_v3_scores_players_profiles_by_team` · `nba_v3_scores.nba_v3_scores_referees` · `nba_v3_scores.nba_v3_scores_schedules` · `nba_v3_scores.nba_v3_scores_schedules_basic` · `nba_v3_scores.nba_v3_scores_season_current` · `nba_v3_scores.nba_v3_scores_stadiums` · `nba_v3_scores.nba_v3_scores_standings` · `nba_v3_scores.nba_v3_scores_team_game_logs_by_season` · `nba_v3_scores.nba_v3_scores_team_game_stats_by_date_live_final` · `nba_v3_scores.nba_v3_scores_team_profiles_all` · `nba_v3_scores.nba_v3_scores_team_profiles_by_active` · `nba_v3_scores.nba_v3_scores_team_profiles_by_season` · `nba_v3_scores.nba_v3_scores_team_season_stats` · `nba_v3_scores.nba_v3_scores_transactions` · `nba_v3_stats.nba_v3_stats_all_stars` · `nba_v3_stats.nba_v3_stats_box_score_final` · `nba_v3_stats.nba_v3_stats_box_score_live_final` · `nba_v3_stats.nba_v3_stats_box_scores_by_date_final` · `nba_v3_stats.nba_v3_stats_box_scores_by_date_live_final` · `nba_v3_stats.nba_v3_stats_box_scores_delta_by_date` · `nba_v3_stats.nba_v3_stats_fantasy_points_by_date` · `nba_v3_stats.nba_v3_stats_player_game_logs_by_season` · `nba_v3_stats.nba_v3_stats_player_game_stats_by_date_final` · `nba_v3_stats.nba_v3_stats_player_game_stats_by_date_live_final` · `nba_v3_stats.nba_v3_stats_player_season_stats` · `nba_v3_stats.nba_v3_stats_player_season_stats_by_team` · `nba_v3_stats.nba_v3_stats_team_game_stats_by_date_final` · `nba_v3_stats.nba_v3_stats_team_stats_allowed_by_position` · `nfl_v3_headshots.nfl_v3_headshots_headshots` · `nfl_v3_odds.nfl_v3_odds_betting_events_by_date` · `nfl_v3_odds.nfl_v3_odds_betting_events_by_season` · `nfl_v3_odds.nfl_v3_odds_betting_futures_by_season` · `nfl_v3_odds.nfl_v3_odds_betting_futures_by_season_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_market` · `nfl_v3_odds.nfl_v3_odds_betting_market_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_event` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_event_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_game` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_game_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_market_type` · `nfl_v3_odds.nfl_v3_odds_betting_markets_by_market_type_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_metadata` · `nfl_v3_odds.nfl_v3_odds_betting_player_props_by_game` · `nfl_v3_odds.nfl_v3_odds_betting_player_props_by_game_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_results_by_market` · `nfl_v3_odds.nfl_v3_odds_betting_results_by_market_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_betting_splits_by_betting_market` · `nfl_v3_odds.nfl_v3_odds_betting_splits_by_game` · `nfl_v3_odds.nfl_v3_odds_betting_trends_by_matchup` · `nfl_v3_odds.nfl_v3_odds_betting_trends_by_team` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_by_week` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_by_week_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_line_movement` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_line_movement_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_period_game_odds_by_week` · `nfl_v3_odds.nfl_v3_odds_period_game_odds_line_movement` · `nfl_v3_odds.nfl_v3_odds_pre_game_and_period_game_odds_by_week_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `nfl_v3_odds.nfl_v3_odds_pre_game_odds_by_week` · `nfl_v3_odds.nfl_v3_odds_pre_game_odds_line_movement` · `nfl_v3_odds.nfl_v3_odds_sportsbooks_active` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_by_team_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_by_team_live_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_live_final` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_delta` · `nfl_v3_play_by_play.nfl_v3_pbp_play_by_play_simulation` · `nfl_v3_projections.nfl_v3_projections_dfs_slate_ownership_projections_by_slate` · `nfl_v3_projections.nfl_v3_projections_dfs_slate_ownership_projections_upcoming` · `nfl_v3_projections.nfl_v3_projections_dfs_slates_by_date` · `nfl_v3_projections.nfl_v3_projections_dfs_slates_by_week` · `nfl_v3_projections.nfl_v3_projections_idp_projected_player_game_stats_by_team` · `nfl_v3_projections.nfl_v3_projections_idp_projected_player_game_stats_by_week` · `nfl_v3_projections.nfl_v3_projections_player_details_by_injured` · `nfl_v3_projections.nfl_v3_projections_projected_fantasy_defense_game_stats_with_dfs_salaries` · `nfl_v3_projections.nfl_v3_projections_projected_fantasy_defense_season_stats_with_adp` · `nfl_v3_projections.nfl_v3_projections_projected_player_game_stats_by_team` · `nfl_v3_projections.nfl_v3_projections_projected_player_game_stats_by_week` · `nfl_v3_projections.nfl_v3_projections_projected_player_season_stats_with_adp` · `nfl_v3_projections.nfl_v3_projections_projected_player_season_stats_with_adp_by_team` · `nfl_v3_roto_baller_premium_news.nfl_v3_news_rotoballer_premium_news` · `nfl_v3_roto_baller_premium_news.nfl_v3_news_rotoballer_premium_news_by_date` · `nfl_v3_roto_baller_premium_news.nfl_v3_news_rotoballer_premium_news_by_team` · `nfl_v3_scores.nfl_v3_scores_are_games_in_progress` · `nfl_v3_scores.nfl_v3_scores_bye_weeks` · `nfl_v3_scores.nfl_v3_scores_depth_charts_all` · `nfl_v3_scores.nfl_v3_scores_depth_charts_by_active` · `nfl_v3_scores.nfl_v3_scores_depth_charts_by_week` · `nfl_v3_scores.nfl_v3_scores_games_by_date_final` · `nfl_v3_scores.nfl_v3_scores_games_by_date_live_final` · `nfl_v3_scores.nfl_v3_scores_games_by_season_live_final` · `nfl_v3_scores.nfl_v3_scores_games_by_week_final` · `nfl_v3_scores.nfl_v3_scores_games_by_week_live_final` · `nfl_v3_scores.nfl_v3_scores_games_basic_by_week_final` · `nfl_v3_scores.nfl_v3_scores_games_basic_by_week_live_final` · `nfl_v3_scores.nfl_v3_scores_news` · `nfl_v3_scores.nfl_v3_scores_news_by_date` · `nfl_v3_scores.nfl_v3_scores_news_by_team` · `nfl_v3_scores.nfl_v3_scores_player_details_all` · `nfl_v3_scores.nfl_v3_scores_player_details_by_free_agents` · `nfl_v3_scores.nfl_v3_scores_player_details_by_rookie_draft_year` · `nfl_v3_scores.nfl_v3_scores_player_details_by_team` · `nfl_v3_scores.nfl_v3_scores_player_game_logs_by_season` · `nfl_v3_scores.nfl_v3_scores_player_profiles_all` · `nfl_v3_scores.nfl_v3_scores_player_profiles_by_free_agent` · `nfl_v3_scores.nfl_v3_scores_player_profiles_by_rookie_draft_year` · `nfl_v3_scores.nfl_v3_scores_player_profiles_by_team` · `nfl_v3_scores.nfl_v3_scores_referees` · `nfl_v3_scores.nfl_v3_scores_schedules` · `nfl_v3_scores.nfl_v3_scores_schedules_basic` · `nfl_v3_scores.nfl_v3_scores_scores_by_week_simulation` · `nfl_v3_scores.nfl_v3_scores_season_current` · `nfl_v3_scores.nfl_v3_scores_season_last_completed` · `nfl_v3_scores.nfl_v3_scores_season_upcoming` · `nfl_v3_scores.nfl_v3_scores_stadiums` · `nfl_v3_scores.nfl_v3_scores_standings` · `nfl_v3_scores.nfl_v3_scores_team_game_stats_live_final` · `nfl_v3_scores.nfl_v3_scores_team_profiles_all` · `nfl_v3_scores.nfl_v3_scores_team_profiles_by_active` · `nfl_v3_scores.nfl_v3_scores_team_profiles_by_season` · `nfl_v3_scores.nfl_v3_scores_team_profiles_basic_all` · `nfl_v3_scores.nfl_v3_scores_team_season_stats` · `nfl_v3_scores.nfl_v3_scores_timeframes` · `nfl_v3_scores.nfl_v3_scores_transactions_by_date` · `nfl_v3_scores.nfl_v3_scores_week_current` · `nfl_v3_scores.nfl_v3_scores_week_last_completed` · `nfl_v3_scores.nfl_v3_scores_week_upcoming` · `nfl_v3_stats.nfl_v3_stats_box_score_by_team_final` · `nfl_v3_stats.nfl_v3_stats_box_score_by_team_live_final` · `nfl_v3_stats.nfl_v3_stats_box_score_final` · `nfl_v3_stats.nfl_v3_stats_box_score_live_final` · `nfl_v3_stats.nfl_v3_stats_box_scores_by_week_final` · `nfl_v3_stats.nfl_v3_stats_box_scores_delta_by_week` · `nfl_v3_stats.nfl_v3_stats_box_scores_simulation` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_game_stats_all` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_game_stats_by_team` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_season_stats_all` · `nfl_v3_stats.nfl_v3_stats_fantasy_defense_season_stats_by_team` · `nfl_v3_stats.nfl_v3_stats_fantasy_player_ownership_percentages_season_long_by_week` · `nfl_v3_stats.nfl_v3_stats_fantasy_points_by_week` · `nfl_v3_stats.nfl_v3_stats_games_by_season_final` · `nfl_v3_stats.nfl_v3_stats_injuries_all` · `nfl_v3_stats.nfl_v3_stats_injuries_by_team` · `nfl_v3_stats.nfl_v3_stats_player_game_logs_by_season` · `nfl_v3_stats.nfl_v3_stats_player_game_red_zone_stats` · `nfl_v3_stats.nfl_v3_stats_player_game_red_zone_stats_inside_five` · `nfl_v3_stats.nfl_v3_stats_player_game_red_zone_stats_inside_ten` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_team_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_team_live_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_week_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_by_week_live_final` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_delta` · `nfl_v3_stats.nfl_v3_stats_player_game_stats_delta_by_week` · `nfl_v3_stats.nfl_v3_stats_player_season_red_zone_stats` · `nfl_v3_stats.nfl_v3_stats_player_season_red_zone_stats_inside_five` · `nfl_v3_stats.nfl_v3_stats_player_season_red_zone_stats_inside_ten` · `nfl_v3_stats.nfl_v3_stats_player_season_stats` · `nfl_v3_stats.nfl_v3_stats_player_season_stats_by_team` · `nfl_v3_stats.nfl_v3_stats_player_season_third_down_stats` · `nfl_v3_stats.nfl_v3_stats_pro_bowlers` · `nfl_v3_stats.nfl_v3_stats_team_game_stats_by_game_final` · `nhl_v3_headshots.nhl_v3_headshots_headshots` · `nhl_v3_odds.nhl_v3_odds_betting_events_by_date` · `nhl_v3_odds.nhl_v3_odds_betting_events_by_season` · `nhl_v3_odds.nhl_v3_odds_betting_futures_by_season` · `nhl_v3_odds.nhl_v3_odds_betting_futures_by_season_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_market` · `nhl_v3_odds.nhl_v3_odds_betting_market_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_event` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_event_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_game` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_game_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_market_type` · `nhl_v3_odds.nhl_v3_odds_betting_markets_by_market_type_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_metadata` · `nhl_v3_odds.nhl_v3_odds_betting_player_props_by_game` · `nhl_v3_odds.nhl_v3_odds_betting_player_props_by_game_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_results_by_market` · `nhl_v3_odds.nhl_v3_odds_betting_results_by_market_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_betting_splits_by_betting_market` · `nhl_v3_odds.nhl_v3_odds_betting_splits_by_game` · `nhl_v3_odds.nhl_v3_odds_betting_trends_by_matchup` · `nhl_v3_odds.nhl_v3_odds_betting_trends_by_team` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_by_date` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_by_date_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_line_movement` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_line_movement_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_period_game_odds_by_date` · `nhl_v3_odds.nhl_v3_odds_period_game_odds_line_movement` · `nhl_v3_odds.nhl_v3_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `nhl_v3_odds.nhl_v3_odds_pre_game_odds_by_date` · `nhl_v3_odds.nhl_v3_odds_pre_game_odds_line_movement` · `nhl_v3_odds.nhl_v3_odds_sportsbooks_by_active` · `nhl_v3_play_by_play.nhl_v3_pbp_play_by_play_final` · `nhl_v3_play_by_play.nhl_v3_pbp_play_by_play_live_final` · `nhl_v3_play_by_play.nhl_v3_pbp_play_by_play_delta` · `nhl_v3_projections.nhl_v3_projections_dfs_slates_by_date` · `nhl_v3_projections.nhl_v3_projections_player_details_by_injured` · `nhl_v3_projections.nhl_v3_projections_projected_player_game_stats_by_date` · `nhl_v3_projections.nhl_v3_projections_starting_goaltenders_by_date` · `nhl_v3_roto_baller_premium_news.nhl_v3_news_rotoballer_premium_news` · `nhl_v3_roto_baller_premium_news.nhl_v3_news_rotoballer_premium_news_by_date` · `nhl_v3_scores.nhl_v3_scores_are_games_in_progress` · `nhl_v3_scores.nhl_v3_scores_depth_charts_goalies` · `nhl_v3_scores.nhl_v3_scores_games_by_date_final` · `nhl_v3_scores.nhl_v3_scores_games_by_date_live_final` · `nhl_v3_scores.nhl_v3_scores_games_basic_by_date_final` · `nhl_v3_scores.nhl_v3_scores_games_basic_by_date_live_final` · `nhl_v3_scores.nhl_v3_scores_news` · `nhl_v3_scores.nhl_v3_scores_news_by_date` · `nhl_v3_scores.nhl_v3_scores_player_details_by_active` · `nhl_v3_scores.nhl_v3_scores_player_details_by_active2` · `nhl_v3_scores.nhl_v3_scores_player_details_by_free_agents` · `nhl_v3_scores.nhl_v3_scores_player_details_by_free_agents2` · `nhl_v3_scores.nhl_v3_scores_players_details_by_team` · `nhl_v3_scores.nhl_v3_scores_players_profiles_by_team` · `nhl_v3_scores.nhl_v3_scores_referees` · `nhl_v3_scores.nhl_v3_scores_schedules` · `nhl_v3_scores.nhl_v3_scores_schedules_basic` · `nhl_v3_scores.nhl_v3_scores_season_current` · `nhl_v3_scores.nhl_v3_scores_stadiums` · `nhl_v3_scores.nhl_v3_scores_standings` · `nhl_v3_scores.nhl_v3_scores_team_game_logs_by_season` · `nhl_v3_scores.nhl_v3_scores_team_game_stats_by_date_live_final` · `nhl_v3_scores.nhl_v3_scores_team_profiles_all` · `nhl_v3_scores.nhl_v3_scores_team_profiles_by_active` · `nhl_v3_scores.nhl_v3_scores_team_profiles_by_season` · `nhl_v3_scores.nhl_v3_scores_team_season_stats` · `nhl_v3_scores.nhl_v3_scores_transactions` · `nhl_v3_stats.nhl_v3_stats_box_score_final` · `nhl_v3_stats.nhl_v3_stats_box_score_live_final` · `nhl_v3_stats.nhl_v3_stats_box_scores_by_date_final` · `nhl_v3_stats.nhl_v3_stats_box_scores_by_date_live_final` · `nhl_v3_stats.nhl_v3_stats_box_scores_delta_by_date` · `nhl_v3_stats.nhl_v3_stats_fantasy_points_by_date` · `nhl_v3_stats.nhl_v3_stats_line_combinations_by_season` · `nhl_v3_stats.nhl_v3_stats_player_game_logs_by_season` · `nhl_v3_stats.nhl_v3_stats_player_game_stats_by_date_final` · `nhl_v3_stats.nhl_v3_stats_player_game_stats_by_date_live_final` · `nhl_v3_stats.nhl_v3_stats_player_season_stats` · `nhl_v3_stats.nhl_v3_stats_player_season_stats_by_team` · `nhl_v3_stats.nhl_v3_stats_team_game_stats_by_date_final` · `nhl_v3_stats.nhl_v3_stats_team_stats_allowed_by_position` · `soccer_v4_headshots.soccer_v4_headshots_headshots` · `soccer_v4_odds.soccer_v4_odds_betting_events_by_date` · `soccer_v4_odds.soccer_v4_odds_betting_events_by_season` · `soccer_v4_odds.soccer_v4_odds_betting_futures_by_season` · `soccer_v4_odds.soccer_v4_odds_betting_futures_by_season_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_market` · `soccer_v4_odds.soccer_v4_odds_betting_market_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_event` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_event_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_game` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_game_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_market_type` · `soccer_v4_odds.soccer_v4_odds_betting_markets_by_market_type_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_betting_metadata` · `soccer_v4_odds.soccer_v4_odds_betting_player_props_by_game` · `soccer_v4_odds.soccer_v4_odds_betting_player_props_by_game_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_by_date` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_by_date_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_line_movement` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_line_movement_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_in_game_odds_line_movement_with_resulting_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_period_game_odds_by_date` · `soccer_v4_odds.soccer_v4_odds_period_game_odds_line_movement` · `soccer_v4_odds.soccer_v4_odds_pre_game_and_period_game_odds_by_date_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_pre_game_and_period_game_odds_line_movement_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_pre_game_and_period_game_odds_line_movement_with_resulting_sportsbook_group` · `soccer_v4_odds.soccer_v4_odds_pre_game_odds_by_date` · `soccer_v4_odds.soccer_v4_odds_pre_game_odds_line_movement` · `soccer_v4_odds.soccer_v4_odds_sportsbooks_by_active` · `soccer_v4_projections.soccer_v4_projections_dfs_slates_by_date` · `soccer_v4_projections.soccer_v4_projections_player_details_by_injured` · `soccer_v4_projections.soccer_v4_projections_projected_player_game_stats_by_date` · `soccer_v4_projections.soccer_v4_projections_upcoming_dfs_slates_by_competition` · `soccer_v4_scores.soccer_v4_scores_areas_countries` · `soccer_v4_scores.soccer_v4_scores_competitions_leagues` · `soccer_v4_scores.soccer_v4_scores_games_by_competition_live_final` · `soccer_v4_scores.soccer_v4_scores_games_by_date_final` · `soccer_v4_scores.soccer_v4_scores_games_by_date_live_final` · `soccer_v4_scores.soccer_v4_scores_games_basic_by_date_final` · `soccer_v4_scores.soccer_v4_scores_games_basic_by_date_live_final` · `soccer_v4_scores.soccer_v4_scores_memberships_by_active` · `soccer_v4_scores.soccer_v4_scores_memberships_by_canceled` · `soccer_v4_scores.soccer_v4_scores_memberships_by_recently_changed` · `soccer_v4_scores.soccer_v4_scores_memberships_historical_by_team` · `soccer_v4_scores.soccer_v4_scores_player_details_by_team` · `soccer_v4_scores.soccer_v4_scores_player_profiles_by_team` · `soccer_v4_scores.soccer_v4_scores_schedules` · `soccer_v4_scores.soccer_v4_scores_schedules_basic` · `soccer_v4_scores.soccer_v4_scores_standings` · `soccer_v4_scores.soccer_v4_scores_team_game_stats_by_date_live_final` · `soccer_v4_scores.soccer_v4_scores_team_profiles_by_competition` · `soccer_v4_scores.soccer_v4_scores_team_profiles_by_season` · `soccer_v4_scores.soccer_v4_scores_team_season_stats` · `soccer_v4_scores.soccer_v4_scores_venues` · `soccer_v4_stats.soccer_v4_stats_box_score_final` · `soccer_v4_stats.soccer_v4_stats_box_score_live_final` · `soccer_v4_stats.soccer_v4_stats_box_scores_by_date_final` · `soccer_v4_stats.soccer_v4_stats_box_scores_by_date_live_final` · `soccer_v4_stats.soccer_v4_stats_box_scores_delta_by_date` · `soccer_v4_stats.soccer_v4_stats_fantasy_points_by_date` · `soccer_v4_stats.soccer_v4_stats_lineups_by_date` · `soccer_v4_stats.soccer_v4_stats_player_game_stats_by_date_final` · `soccer_v4_stats.soccer_v4_stats_player_game_stats_by_date_live_final` · `soccer_v4_stats.soccer_v4_stats_player_season_stats` · `soccer_v4_stats.soccer_v4_stats_team_game_stats_by_date_final` · `wnba_v3_scores.wnba_v3_scores_are_games_in_progress` · `wnba_v3_scores.wnba_v3_scores_betting_events_by_date` · `wnba_v3_scores.wnba_v3_scores_betting_events_by_season` · `wnba_v3_scores.wnba_v3_scores_betting_futures_by_season` · `wnba_v3_scores.wnba_v3_scores_betting_futures_by_season_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_market` · `wnba_v3_scores.wnba_v3_scores_betting_market_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_event` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_event_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_game` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_game_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_market_type` · `wnba_v3_scores.wnba_v3_scores_betting_markets_by_market_type_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_betting_metadata` · `wnba_v3_scores.wnba_v3_scores_betting_player_props_by_game` · `wnba_v3_scores.wnba_v3_scores_betting_player_props_by_game_sportsbook_group` · `wnba_v3_scores.wnba_v3_scores_box_score_final` · `wnba_v3_scores.wnba_v3_scores_box_score_live_final` · `wnba_v3_scores.wnba_v3_scores_box_scores_by_date_final` · `wnba_v3_scores.wnba_v3_scores_box_scores_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_games_by_date_final` · `wnba_v3_scores.wnba_v3_scores_games_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_games_basic_by_date_final` · `wnba_v3_scores.wnba_v3_scores_games_basic_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_player_details_by_active` · `wnba_v3_scores.wnba_v3_scores_player_details_by_team` · `wnba_v3_scores.wnba_v3_scores_player_season_stats` · `wnba_v3_scores.wnba_v3_scores_player_season_stats_by_team` · `wnba_v3_scores.wnba_v3_scores_pre_game_odds_by_date` · `wnba_v3_scores.wnba_v3_scores_pre_game_odds_line_movement` · `wnba_v3_scores.wnba_v3_scores_schedules` · `wnba_v3_scores.wnba_v3_scores_schedules_basic` · `wnba_v3_scores.wnba_v3_scores_season_current` · `wnba_v3_scores.wnba_v3_scores_sportsbooks_by_active` · `wnba_v3_scores.wnba_v3_scores_stadiums` · `wnba_v3_scores.wnba_v3_scores_standings` · `wnba_v3_scores.wnba_v3_scores_team_game_stats_by_date_final` · `wnba_v3_scores.wnba_v3_scores_team_game_stats_by_date_live_final` · `wnba_v3_scores.wnba_v3_scores_team_profiles_all` · `wnba_v3_scores.wnba_v3_scores_team_season_stats`); distinguishing members *none required* + 2. So `isinstance(e.error, Unauthorized)` matches only 741 of 741 operations. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares no required member on any return type, so a truncated body decodes without complaint and the hole surfaces later.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which host the `base_url` selects**, because omitting it is silently the default. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/tesla/.claude-plugin/plugin.json b/plugins/tesla/.claude-plugin/plugin.json index f40d1b54..32ee79ad 100644 --- a/plugins/tesla/.claude-plugin/plugin.json +++ b/plugins/tesla/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "tesla", "displayName": "Tesla Fleet Management API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Tesla Fleet Management API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/tesla/.cursor-plugin/plugin.json b/plugins/tesla/.cursor-plugin/plugin.json index f40d1b54..32ee79ad 100644 --- a/plugins/tesla/.cursor-plugin/plugin.json +++ b/plugins/tesla/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "tesla", "displayName": "Tesla Fleet Management API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Tesla Fleet Management API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/tesla/README.md b/plugins/tesla/README.md index c10da0b8..dd0fd430 100644 --- a/plugins/tesla/README.md +++ b/plugins/tesla/README.md @@ -1,13 +1,13 @@ -# Tesla Fleet Management API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Tesla Fleet Management API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-tesla`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-tesla`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-tesla`, `typescript-models`, `typescript-testing` | +# Tesla Fleet Management API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Tesla Fleet Management API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-tesla`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-tesla`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-tesla`, `typescript-models`, `typescript-testing` | diff --git a/plugins/tesla/plugin.json b/plugins/tesla/plugin.json index f40d1b54..32ee79ad 100644 --- a/plugins/tesla/plugin.json +++ b/plugins/tesla/plugin.json @@ -1,7 +1,7 @@ { "name": "tesla", "displayName": "Tesla Fleet Management API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Tesla Fleet Management API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/tesla/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/tesla/skills/dotnet/dotnet-getting-started/SKILL.md index 432c0610..2346e093 100644 --- a/plugins/tesla/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/tesla/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Tesla Fleet Management API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-tesla-fleet-management-api is the workflow that uses it." ---- - -# Getting started with the Tesla Fleet Management API .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Tesla Fleet Management API SDK fact. `dotnet-integrate-tesla-fleet-management-api` is the workflow that drives it — you collect the contracts for the work in scope into `tesla-fleet-management-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Tesla Fleet Management API | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/tesla-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `TeslaFleetManagementApi` (the `using` namespace) | -| Client class | `TeslaFleetManagementApiClient` | -| Options class | `TeslaFleetManagementApiClientOptions` | -| Auth | **Bearer** token — set `BearerAuth`
**OAuth 2.0** authorization code — set `ThirdpartytokenAuthorizationCode`
**OAuth 2.0** client credentials — set `ThirdpartytokenClientCredentials` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://fleet-api.prd.na.vn.cloud.tesla.com`
`ServerEnvironment.Environment2` → `https://auth.tesla.com/oauth2/v3` | -| Log environment variable | `TESLAFLEETMANAGEMENTAPICLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-tesla-fleet-management-api` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using TeslaFleetManagementApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `TeslaFleetManagementApiClient` | `TeslaFleetManagementApi` | -| `TeslaFleetManagementApiClientOptions` | `TeslaFleetManagementApi` | -| `ServerEnvironment` | `TeslaFleetManagementApi.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Tesla Fleet Management API, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/TeslaFleetManagementApi/Models/`, `src/TeslaFleetManagementApi/Errors/` and `src/TeslaFleetManagementApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/TeslaFleetManagementApi/Models/`, enums `src/TeslaFleetManagementApi/Models/Enums/`, typed errors `src/TeslaFleetManagementApi/Errors/`, request records `src/TeslaFleetManagementApi/Requests/`, or `src/TeslaFleetManagementApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `tesla-fleet-management-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/tesla-fleet-management-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/tesla-fleet-management-api-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/tesla-fleet-management-api-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/tesla-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\tesla-fleet-management-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/tesla-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `tesla-fleet-management-api-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/TeslaFleetManagementApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/TeslaFleetManagementApi/Models/` (+ `src/TeslaFleetManagementApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/TeslaFleetManagementApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/TeslaFleetManagementApi/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/TeslaFleetManagementApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/TeslaFleetManagementApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/TeslaFleetManagementApi/Servers/`, `src/TeslaFleetManagementApi/TeslaFleetManagementApiClient.cs`, `src/TeslaFleetManagementApi/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Tesla Fleet Management API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `TESLAFLEETMANAGEMENTAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/TeslaFleetManagementApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-tesla-fleet-management-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new TeslaFleetManagementApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `TeslaFleetManagementApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Tesla Fleet Management API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-tesla-fleet-management-api is the workflow that uses it." +--- + +# Getting started with the Tesla Fleet Management API .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Tesla Fleet Management API SDK fact. `dotnet-integrate-tesla-fleet-management-api` is the workflow that drives it — you collect the contracts for the work in scope into `tesla-fleet-management-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Tesla Fleet Management API | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/tesla-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `TeslaFleetManagementApi` (the `using` namespace) | +| Client class | `TeslaFleetManagementApiClient` | +| Options class | `TeslaFleetManagementApiClientOptions` | +| Auth | **Bearer** token — set `BearerAuth`
**OAuth 2.0** authorization code — set `ThirdpartytokenAuthorizationCode`
**OAuth 2.0** client credentials — set `ThirdpartytokenClientCredentials` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://fleet-api.prd.na.vn.cloud.tesla.com`
`ServerEnvironment.Environment2` → `https://auth.tesla.com/oauth2/v3` | +| Log environment variable | `TESLAFLEETMANAGEMENTAPICLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-tesla-fleet-management-api` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using TeslaFleetManagementApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `TeslaFleetManagementApiClient` | `TeslaFleetManagementApi` | +| `TeslaFleetManagementApiClientOptions` | `TeslaFleetManagementApi` | +| `ServerEnvironment` | `TeslaFleetManagementApi.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Tesla Fleet Management API, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/TeslaFleetManagementApi/Models/`, `src/TeslaFleetManagementApi/Errors/` and `src/TeslaFleetManagementApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/TeslaFleetManagementApi/Models/`, enums `src/TeslaFleetManagementApi/Models/Enums/`, typed errors `src/TeslaFleetManagementApi/Errors/`, request records `src/TeslaFleetManagementApi/Requests/`, or `src/TeslaFleetManagementApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `tesla-fleet-management-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/tesla-fleet-management-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/tesla-fleet-management-api-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/tesla-fleet-management-api-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/tesla-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\tesla-fleet-management-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/tesla-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `tesla-fleet-management-api-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/TeslaFleetManagementApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/TeslaFleetManagementApi/Models/` (+ `src/TeslaFleetManagementApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/TeslaFleetManagementApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/TeslaFleetManagementApi/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/TeslaFleetManagementApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/TeslaFleetManagementApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/TeslaFleetManagementApi/Servers/`, `src/TeslaFleetManagementApi/TeslaFleetManagementApiClient.cs`, `src/TeslaFleetManagementApi/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Tesla Fleet Management API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `TESLAFLEETMANAGEMENTAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/TeslaFleetManagementApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-tesla-fleet-management-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new TeslaFleetManagementApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `TeslaFleetManagementApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/tesla/skills/python/python-getting-started/SKILL.md b/plugins/tesla/skills/python/python-getting-started/SKILL.md index 313cef6e..71ae44bc 100644 --- a/plugins/tesla/skills/python/python-getting-started/SKILL.md +++ b/plugins/tesla/skills/python/python-getting-started/SKILL.md @@ -1,243 +1,243 @@ ---- -name: "python-getting-started" -description: "Tesla Fleet Management API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Tesla Fleet Management API Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Tesla Fleet Management API Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Tesla Fleet Management API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-tesla-fleet-management-api` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `tesla_fleet_management_api/` and `pyproject.toml` of the generated package at version `1.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Tesla Fleet Management API | -| Distribution name (what you install) | `tesla-fleet-management-api` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `tesla_fleet_management_api` — not the string you install | -| Source repository | https://github.com/context-plugins/tesla-python-sdk | -| Source branch | `main` | -| Version | `1.0.0` | -| Sync client class | `TeslaFleetManagementApiClient` (alias `Client`) | -| Async client class | `AsyncTeslaFleetManagementApiClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `bearer_auth` · `thirdpartytoken_authorization_code` · `thirdpartytoken_authorization_code_token_source` · `thirdpartytoken_client_credentials` · `thirdpartytoken_client_credentials_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **Bearer** token — set `bearer_auth` · **OAuth 2.0** authorization code — set `thirdpartytoken_authorization_code` · **OAuth 2.0** client credentials — set `thirdpartytoken_client_credentials` | -| Environments | 2 environments selected by `environment` (default `"production"`), overridable with `base_url` | -| Base-URL config | `ServerConfig` (`tesla_fleet_management_api/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 64 operations across 6 controllers · 84 models · 0 unions · 4 enums · 0 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "tesla-fleet-management-api @ git+https://github.com/context-plugins/tesla-python-sdk.git@main" -python -c "import tesla_fleet_management_api, pathlib; print(pathlib.Path(tesla_fleet_management_api.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from tesla_fleet_management_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`tesla_fleet_management_api/__init__.py` exports exactly 6 names beside the `tesla_fleet_management_api.models` subpackage it re-exports: - -```python -from tesla_fleet_management_api import ( - AsyncClient, - AsyncTeslaFleetManagementApiClient, - Client, - Environment, - ServerConfig, - TeslaFleetManagementApiClient, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `tesla_fleet_management_api.models` | -| Enums (and their open `…OrStr` aliases) | `tesla_fleet_management_api.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `AuthorizationCodeCredentials` · `ClientCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `tesla_fleet_management_api.core` | -| Per-operation error *unions* | `tesla_fleet_management_api.errors` — *this SDK declares none* | - -`tesla_fleet_management_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments - -The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: - -| `environment=` | Base URL | Hosting | -| --- | --- | --- | -| `"production"` *(default)* | `https://fleet-api.prd.na.vn.cloud.tesla.com` | Production | -| `"environment2"` | `https://auth.tesla.com/oauth2/v3` | Production | - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `environment` gives you **`"production"`**, silently — it is whatever the description listed first, not necessarily production. -- `base_url` overrides the environment's URL entirely when both are passed. -- The token endpoint is derived from the same base URL (`/token`), so it always follows the environment — you never configure it separately. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (three schemes) - -A bearer token, exposed as the client's `bearer_auth=` keyword taking a plain string. - -```python -from tesla_fleet_management_api import Client - -client = Client(bearer_auth="") -``` - -OAuth 2.0 authorization code, exposed as the client's `thirdpartytoken_authorization_code=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/token`. - -```python -from tesla_fleet_management_api import Client -from tesla_fleet_management_api.core import AuthorizationCodeCredentials - -def prompt(url: str) -> str: - return input(f"Open {url}, then paste the code: ") - -client = Client(thirdpartytoken_authorization_code=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) -client = Client(thirdpartytoken_authorization_code={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent -``` - -OAuth 2.0 client credentials, exposed as the client's `thirdpartytoken_client_credentials=` keyword taking `ClientCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/token`. - -```python -from tesla_fleet_management_api import Client -from tesla_fleet_management_api.core import ClientCredentials - -client = Client(thirdpartytoken_client_credentials=ClientCredentials(client_id="…", client_secret="…")) -client = Client(thirdpartytoken_client_credentials={"client_id": "…", "client_secret": "…"}) # equivalent -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.charging` | `Charging` / `AsyncCharging` | 3 | `get_charging_history` · `get_charging_invoice` · `get_charging_sessions` | -| `client.energy` | `Energy` / `AsyncEnergy` | 11 | `adjust_site_s_backup_reserve` · `adjust_site_s_off_grid_vehicle_charging_reserve` · `allow_disallow_charging_from_the_grid_and_exporting_energy_to_the_grid` · `get_backup_or_energy_history` · `get_live_site_status` · `get_site_information_assets_settings_features` · … | -| `client.partner` | `Partner` / `AsyncPartner` | 4 | `get_public_key_for_a_domain` · `get_recent_fleet_telemetry_errors` · `get_vins_with_fleet_telemetry_errors` · `register_a_partner_account` | -| `client.user` | `User` / `AsyncUser` | 4 | `get_active_orders_for_a_user` · `get_custom_feature_flags_for_a_user` · `get_summary_of_a_user_s_account` · `get_user_s_region_and_fleet_api_base_url` | -| `client.vehicle_commands` | `VehicleCommands` / `AsyncVehicleCommands` | 21 | `actuatetrunk` · `addchargeschedule` · `addpreconditionschedule` · `adjustmediavolume` · `cancelsoftwareupdate` · `chargemaxrange` · … | -| `client.vehicles` | `Vehicles` / `AsyncVehicles` | 21 | `configure_fleet_telemetry_using_signed_jws_token` · `create_or_update_fleet_telemetry_configuration` · `delete_fleet_telemetry_configuration` · `get_allowed_drivers_for_a_vehicle` · `get_eligible_vehicle_subscriptions` · `get_eligible_vehicle_upgrades` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `tesla_fleet_management_api/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `tesla_fleet_management_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/tesla-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `tesla_fleet_management_api/models/actuate_trunk_request.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `tesla_fleet_management_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import tesla_fleet_management_api, pathlib; print(pathlib.Path(tesla_fleet_management_api.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/tesla_fleet_management_api` on Windows, `.venv/lib/python3.*/site-packages/tesla_fleet_management_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The three schemes are `bearer_auth=`, `thirdpartytoken_authorization_code=` and `thirdpartytoken_client_credentials=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **2 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 2 operations that return `None`** — `charging.get_charging_invoice` · `vehicles.set_enterprise_payer_roles`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 19 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"` of the 2 declared. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Tesla Fleet Management API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Tesla Fleet Management API Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Tesla Fleet Management API Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Tesla Fleet Management API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-tesla-fleet-management-api` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `tesla_fleet_management_api/` and `pyproject.toml` of the generated package at version `1.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Tesla Fleet Management API | +| Distribution name (what you install) | `tesla-fleet-management-api` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `tesla_fleet_management_api` — not the string you install | +| Source repository | https://github.com/context-plugins/tesla-python-sdk | +| Source branch | `main` | +| Version | `1.0.0` | +| Sync client class | `TeslaFleetManagementApiClient` (alias `Client`) | +| Async client class | `AsyncTeslaFleetManagementApiClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `environment` · `base_url` · `timeout` (default `30.0`) · `retry_options` · `bearer_auth` · `thirdpartytoken_authorization_code` · `thirdpartytoken_authorization_code_token_source` · `thirdpartytoken_client_credentials` · `thirdpartytoken_client_credentials_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **Bearer** token — set `bearer_auth` · **OAuth 2.0** authorization code — set `thirdpartytoken_authorization_code` · **OAuth 2.0** client credentials — set `thirdpartytoken_client_credentials` | +| Environments | 2 environments selected by `environment` (default `"production"`), overridable with `base_url` | +| Base-URL config | `ServerConfig` (`tesla_fleet_management_api/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 64 operations across 6 controllers · 84 models · 0 unions · 4 enums · 0 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "tesla-fleet-management-api @ git+https://github.com/context-plugins/tesla-python-sdk.git@main" +python -c "import tesla_fleet_management_api, pathlib; print(pathlib.Path(tesla_fleet_management_api.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from tesla_fleet_management_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`tesla_fleet_management_api/__init__.py` exports exactly 6 names beside the `tesla_fleet_management_api.models` subpackage it re-exports: + +```python +from tesla_fleet_management_api import ( + AsyncClient, + AsyncTeslaFleetManagementApiClient, + Client, + Environment, + ServerConfig, + TeslaFleetManagementApiClient, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `tesla_fleet_management_api.models` | +| Enums (and their open `…OrStr` aliases) | `tesla_fleet_management_api.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `AuthorizationCodeCredentials` · `ClientCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `tesla_fleet_management_api.core` | +| Per-operation error *unions* | `tesla_fleet_management_api.errors` — *this SDK declares none* | + +`tesla_fleet_management_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments + +The constructor takes an `environment` keyword — a **string literal alias**, not an enum — followed by `base_url`. The declared environments and the base URL each resolves to: + +| `environment=` | Base URL | Hosting | +| --- | --- | --- | +| `"production"` *(default)* | `https://fleet-api.prd.na.vn.cloud.tesla.com` | Production | +| `"environment2"` | `https://auth.tesla.com/oauth2/v3` | Production | + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `environment` gives you **`"production"`**, silently — it is whatever the description listed first, not necessarily production. +- `base_url` overrides the environment's URL entirely when both are passed. +- The token endpoint is derived from the same base URL (`/token`), so it always follows the environment — you never configure it separately. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (three schemes) + +A bearer token, exposed as the client's `bearer_auth=` keyword taking a plain string. + +```python +from tesla_fleet_management_api import Client + +client = Client(bearer_auth="") +``` + +OAuth 2.0 authorization code, exposed as the client's `thirdpartytoken_authorization_code=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/token`. + +```python +from tesla_fleet_management_api import Client +from tesla_fleet_management_api.core import AuthorizationCodeCredentials + +def prompt(url: str) -> str: + return input(f"Open {url}, then paste the code: ") + +client = Client(thirdpartytoken_authorization_code=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) +client = Client(thirdpartytoken_authorization_code={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent +``` + +OAuth 2.0 client credentials, exposed as the client's `thirdpartytoken_client_credentials=` keyword taking `ClientCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/token`. + +```python +from tesla_fleet_management_api import Client +from tesla_fleet_management_api.core import ClientCredentials + +client = Client(thirdpartytoken_client_credentials=ClientCredentials(client_id="…", client_secret="…")) +client = Client(thirdpartytoken_client_credentials={"client_id": "…", "client_secret": "…"}) # equivalent +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.charging` | `Charging` / `AsyncCharging` | 3 | `get_charging_history` · `get_charging_invoice` · `get_charging_sessions` | +| `client.energy` | `Energy` / `AsyncEnergy` | 11 | `adjust_site_s_backup_reserve` · `adjust_site_s_off_grid_vehicle_charging_reserve` · `allow_disallow_charging_from_the_grid_and_exporting_energy_to_the_grid` · `get_backup_or_energy_history` · `get_live_site_status` · `get_site_information_assets_settings_features` · … | +| `client.partner` | `Partner` / `AsyncPartner` | 4 | `get_public_key_for_a_domain` · `get_recent_fleet_telemetry_errors` · `get_vins_with_fleet_telemetry_errors` · `register_a_partner_account` | +| `client.user` | `User` / `AsyncUser` | 4 | `get_active_orders_for_a_user` · `get_custom_feature_flags_for_a_user` · `get_summary_of_a_user_s_account` · `get_user_s_region_and_fleet_api_base_url` | +| `client.vehicle_commands` | `VehicleCommands` / `AsyncVehicleCommands` | 21 | `actuatetrunk` · `addchargeschedule` · `addpreconditionschedule` · `adjustmediavolume` · `cancelsoftwareupdate` · `chargemaxrange` · … | +| `client.vehicles` | `Vehicles` / `AsyncVehicles` | 21 | `configure_fleet_telemetry_using_signed_jws_token` · `create_or_update_fleet_telemetry_configuration` · `delete_fleet_telemetry_configuration` · `get_allowed_drivers_for_a_vehicle` · `get_eligible_vehicle_subscriptions` · `get_eligible_vehicle_upgrades` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `tesla_fleet_management_api/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `tesla_fleet_management_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/tesla-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `tesla_fleet_management_api/models/actuate_trunk_request.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `tesla_fleet_management_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import tesla_fleet_management_api, pathlib; print(pathlib.Path(tesla_fleet_management_api.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/tesla_fleet_management_api` on Windows, `.venv/lib/python3.*/site-packages/tesla_fleet_management_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The three schemes are `bearer_auth=`, `thirdpartytoken_authorization_code=` and `thirdpartytoken_client_credentials=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **2 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 2 operations that return `None`** — `charging.get_charging_invoice` · `vehicles.set_enterprise_payer_roles`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — no operation in this SDK documents a typed error body, so `.error` is always `RawError`. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 19 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which environment the `environment` keyword selects**, because omitting it is silently `"production"` of the 2 declared. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/twilio/.claude-plugin/plugin.json b/plugins/twilio/.claude-plugin/plugin.json index 9c25d20d..38f03494 100644 --- a/plugins/twilio/.claude-plugin/plugin.json +++ b/plugins/twilio/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "twilio", "displayName": "Twilio SDK", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated Twilio SDK SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/twilio/.cursor-plugin/plugin.json b/plugins/twilio/.cursor-plugin/plugin.json index 9c25d20d..38f03494 100644 --- a/plugins/twilio/.cursor-plugin/plugin.json +++ b/plugins/twilio/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "twilio", "displayName": "Twilio SDK", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated Twilio SDK SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/twilio/plugin.json b/plugins/twilio/plugin.json index 9c25d20d..38f03494 100644 --- a/plugins/twilio/plugin.json +++ b/plugins/twilio/plugin.json @@ -1,7 +1,7 @@ { "name": "twilio", "displayName": "Twilio SDK", - "version": "0.4.0", + "version": "0.4.1", "description": "Integrate and consume the APIMatic-generated Twilio SDK SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/twilio/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/twilio/skills/dotnet/dotnet-getting-started/SKILL.md index 97b68d6a..422170a9 100644 --- a/plugins/twilio/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/twilio/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Twilio SDK .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-twilio-sdk is the workflow that uses it." ---- - -# Getting started with the Twilio SDK .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Twilio SDK SDK fact. `dotnet-integrate-twilio-sdk` is the workflow that drives it — you collect the contracts for the work in scope into `twilio-sdk-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Twilio SDK | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/twilio-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `TwilioSdk` (the `using` namespace) | -| Client class | `TwilioSdkClient` | -| Options class | `TwilioSdkClientOptions` | -| Auth | HTTP **Basic** — set `AccountSidAuthToken` | -| Environments | `ServerEnvironment.Production` *(default)* → `Default` `https://api.twilio.com`, `Default1` `https://messaging.twilio.com`, `Default2` `https://content.twilio.com`, `Default3` `https://verify.twilio.com`, `Default4` `https://lookups.twilio.com`, `Default5` `https://numbers.twilio.com`, `Default6` `https://video.twilio.com`, `Default7` `https://conversations.twilio.com`, `Default8` `https://taskrouter.twilio.com`, `Default9` `https://trusthub.twilio.com`, `Default10` `https://proxy.twilio.com`, `Default11` `https://studio.twilio.com`, `Default12` `https://sync.twilio.com`, `Default13` `https://flex-api.twilio.com`, `Default14` `https://insights.twilio.com` | -| Log environment variable | `TWILIOSDKCLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-twilio-sdk` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using TwilioSdk.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `TwilioSdkClient` | `TwilioSdk` | -| `TwilioSdkClientOptions` | `TwilioSdk` | -| `ServerEnvironment` | `TwilioSdk.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Twilio SDK, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/TwilioSdk/Models/`, `src/TwilioSdk/Errors/` and `src/TwilioSdk/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/TwilioSdk/Models/`, enums `src/TwilioSdk/Models/Enums/`, typed errors `src/TwilioSdk/Errors/`, request records `src/TwilioSdk/Requests/`, or `src/TwilioSdk/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `twilio-sdk-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/twilio-sdk-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/twilio-sdk-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/twilio-sdk-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/twilio-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\twilio-sdk-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/twilio-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `twilio-sdk-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/TwilioSdk/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/TwilioSdk/Models/` (+ `src/TwilioSdk/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/TwilioSdk/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/TwilioSdk/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/TwilioSdk/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/TwilioSdk/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/TwilioSdk/Servers/`, `src/TwilioSdk/TwilioSdkClient.cs`, `src/TwilioSdk/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Twilio SDK may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `TWILIOSDKCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/TwilioSdk/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-twilio-sdk`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new TwilioSdkClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `TwilioSdkClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Twilio SDK .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-twilio-sdk is the workflow that uses it." +--- + +# Getting started with the Twilio SDK .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Twilio SDK SDK fact. `dotnet-integrate-twilio-sdk` is the workflow that drives it — you collect the contracts for the work in scope into `twilio-sdk-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Twilio SDK | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/twilio-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `TwilioSdk` (the `using` namespace) | +| Client class | `TwilioSdkClient` | +| Options class | `TwilioSdkClientOptions` | +| Auth | HTTP **Basic** — set `AccountSidAuthToken` | +| Environments | `ServerEnvironment.Production` *(default)* → `Default` `https://api.twilio.com`, `Default1` `https://messaging.twilio.com`, `Default2` `https://content.twilio.com`, `Default3` `https://verify.twilio.com`, `Default4` `https://lookups.twilio.com`, `Default5` `https://numbers.twilio.com`, `Default6` `https://video.twilio.com`, `Default7` `https://conversations.twilio.com`, `Default8` `https://taskrouter.twilio.com`, `Default9` `https://trusthub.twilio.com`, `Default10` `https://proxy.twilio.com`, `Default11` `https://studio.twilio.com`, `Default12` `https://sync.twilio.com`, `Default13` `https://flex-api.twilio.com`, `Default14` `https://insights.twilio.com` | +| Log environment variable | `TWILIOSDKCLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-twilio-sdk` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using TwilioSdk.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `TwilioSdkClient` | `TwilioSdk` | +| `TwilioSdkClientOptions` | `TwilioSdk` | +| `ServerEnvironment` | `TwilioSdk.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Twilio SDK, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/TwilioSdk/Models/`, `src/TwilioSdk/Errors/` and `src/TwilioSdk/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/TwilioSdk/Models/`, enums `src/TwilioSdk/Models/Enums/`, typed errors `src/TwilioSdk/Errors/`, request records `src/TwilioSdk/Requests/`, or `src/TwilioSdk/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `twilio-sdk-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/twilio-sdk-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/twilio-sdk-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/twilio-sdk-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/twilio-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\twilio-sdk-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/twilio-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `twilio-sdk-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/TwilioSdk/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/TwilioSdk/Models/` (+ `src/TwilioSdk/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/TwilioSdk/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/TwilioSdk/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/TwilioSdk/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/TwilioSdk/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/TwilioSdk/Servers/`, `src/TwilioSdk/TwilioSdkClient.cs`, `src/TwilioSdk/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Twilio SDK may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `TWILIOSDKCLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/TwilioSdk/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-twilio-sdk`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new TwilioSdkClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `TwilioSdkClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/twilio/skills/python/python-getting-started/SKILL.md b/plugins/twilio/skills/python/python-getting-started/SKILL.md index 2d6f1f11..35550b90 100644 --- a/plugins/twilio/skills/python/python-getting-started/SKILL.md +++ b/plugins/twilio/skills/python/python-getting-started/SKILL.md @@ -1,556 +1,556 @@ ---- -name: "python-getting-started" -description: "Twilio SDK Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Twilio SDK Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Twilio SDK Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Twilio SDK Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-twilio-sdk` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `twilio_sdk/` and `pyproject.toml` of the generated package at version `1.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Twilio SDK | -| Distribution name (what you install) | `twilio-sdk` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `twilio_sdk` — not the string you install | -| Source repository | https://github.com/context-plugins/twilio-python-sdk | -| Source branch | `main` | -| Version | `1.0.0` | -| Sync client class | `TwilioSdkClient` (alias `Client`) | -| Async client class | `AsyncTwilioSdkClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `server_config` · `timeout` (default `30.0`) · `retry_options` · `account_sid_auth_token`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | HTTP **Basic** — set `account_sid_auth_token` | -| Environments | one environment; 15 named servers configured through `server_config` | -| Base-URL config | `ServerConfig` (`twilio_sdk/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 898 operations across 318 controllers, plus 11 directly on the client (`map/operations/client.md`) · 838 models · 4 unions · 399 enums · 37 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "twilio-sdk @ git+https://github.com/context-plugins/twilio-python-sdk.git@main" -python -c "import twilio_sdk, pathlib; print(pathlib.Path(twilio_sdk.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from twilio_sdk import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`twilio_sdk/__init__.py` exports exactly 7 names beside the `twilio_sdk.models` subpackage it re-exports: - -```python -from twilio_sdk import ( - AsyncClient, - AsyncTwilioSdkClient, - Client, - ServerConfig, - ServerConfigDict, - ServerConfigOrDict, - TwilioSdkClient, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `twilio_sdk.models` | -| Enums (and their open `…OrStr` aliases) | `twilio_sdk.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `BasicAuthCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `twilio_sdk.core` | -| Per-operation error *unions* | `twilio_sdk.errors` (`CreateCommunicationInConversationErrorBody`, …) | - -`twilio_sdk.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Servers — one config object, several servers - -The API declares 15 named servers and a single environment, so the constructor takes `server_config: ServerConfigOrDict | None = None` rather than a bare `base_url`. Each server is a field on that config: - -| Server field | Base URL | -| --- | --- | -| `default` | `https://api.twilio.com` | -| `default1` | `https://messaging.twilio.com` | -| `default2` | `https://content.twilio.com` | -| `default3` | `https://verify.twilio.com` | -| `default4` | `https://lookups.twilio.com` | -| `default5` | `https://numbers.twilio.com` | -| `default6` | `https://video.twilio.com` | -| `default7` | `https://conversations.twilio.com` | -| `default8` | `https://taskrouter.twilio.com` | -| `default9` | `https://trusthub.twilio.com` | -| `default10` | `https://proxy.twilio.com` | -| `default11` | `https://studio.twilio.com` | -| `default12` | `https://sync.twilio.com` | -| `default13` | `https://flex-api.twilio.com` | -| `default14` | `https://insights.twilio.com` | - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `server_config` falls through to the config's own defaults rather than writing `None` over them. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (one scheme) - -HTTP Basic authentication, exposed as the client's `account_sid_auth_token=` keyword taking `BasicAuthCredentials` **or a plain dict**. - -```python -from twilio_sdk import Client -from twilio_sdk.core import BasicAuthCredentials - -client = Client(account_sid_auth_token=BasicAuthCredentials(username="…", password="…")) -client = Client(account_sid_auth_token={"username": "…", "password": "…"}) # equivalent -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.api20100401_account` | `Api20100401Account` / `AsyncApi20100401Account` | 4 | `create_account` · `fetch_account` · `list_account` · `update_account` | -| `client.api20100401_add_on_result` | `Api20100401AddOnResult` / `AsyncApi20100401AddOnResult` | 3 | `delete_recording_add_on_result` · `fetch_recording_add_on_result` · `list_recording_add_on_result` | -| `client.api20100401_address` | `Api20100401Address` / `AsyncApi20100401Address` | 5 | `create_address` · `delete_address` · `fetch_address` · `list_address` · `update_address` | -| `client.api20100401_all_time` | `Api20100401AllTime` / `AsyncApi20100401AllTime` | 1 | `list_usage_record_all_time` | -| `client.api20100401_application` | `Api20100401Application` / `AsyncApi20100401Application` | 5 | `create_application` · `delete_application` · `fetch_application` · `list_application` · `update_application` | -| `client.api20100401_assigned_add_on` | `Api20100401AssignedAddOn` / `AsyncApi20100401AssignedAddOn` | 4 | `create_incoming_phone_number_assigned_add_on` · `delete_incoming_phone_number_assigned_add_on` · `fetch_incoming_phone_number_assigned_add_on` · `list_incoming_phone_number_assigned_add_on` | -| `client.api20100401_assigned_add_on_extension` | `Api20100401AssignedAddOnExtension` / `AsyncApi20100401AssignedAddOnExtension` | 2 | `fetch_incoming_phone_number_assigned_add_on_extension` · `list_incoming_phone_number_assigned_add_on_extension` | -| `client.api20100401_auth_calls_credential_list_mapping` | `Api20100401AuthCallsCredentialListMapping` / `AsyncApi20100401AuthCallsCredentialListMapping` | 4 | `create_sip_auth_calls_credential_list_mapping` · `delete_sip_auth_calls_credential_list_mapping` · `fetch_sip_auth_calls_credential_list_mapping` · `list_sip_auth_calls_credential_list_mapping` | -| `client.api20100401_auth_calls_ip_access_control_list_mapping` | `Api20100401AuthCallsIpAccessControlListMapping` / `AsyncApi20100401AuthCallsIpAccessControlListMapping` | 4 | `create_sip_auth_calls_ip_access_control_list_mapping` · `delete_sip_auth_calls_ip_access_control_list_mapping` · `fetch_sip_auth_calls_ip_access_control_list_mapping` · `list_sip_auth_calls_ip_access_control_list_mapping` | -| `client.api20100401_auth_registrations_credential_list_mapping` | `Api20100401AuthRegistrationsCredentialListMapping` / `AsyncApi20100401AuthRegistrationsCredentialListMapping` | 4 | `create_sip_auth_registrations_credential_list_mapping` · `delete_sip_auth_registrations_credential_list_mapping` · `fetch_sip_auth_registrations_credential_list_mapping` · `list_sip_auth_registrations_credential_list_mapping` | -| `client.api20100401_authorized_connect_app` | `Api20100401AuthorizedConnectApp` / `AsyncApi20100401AuthorizedConnectApp` | 2 | `fetch_authorized_connect_app` · `list_authorized_connect_app` | -| `client.api20100401_available_phone_number_country` | `Api20100401AvailablePhoneNumberCountry` / `AsyncApi20100401AvailablePhoneNumberCountry` | 2 | `fetch_available_phone_number_country` · `list_available_phone_number_country` | -| `client.api20100401_balance` | `Api20100401Balance` / `AsyncApi20100401Balance` | 1 | `fetch_balance` | -| `client.api20100401_call` | `Api20100401Call` / `AsyncApi20100401Call` | 5 | `create_call` · `delete_call` · `fetch_call` · `list_call` · `update_call` | -| `client.api20100401_call_notification` | `Api20100401CallNotification` / `AsyncApi20100401CallNotification` | 2 | `fetch_call_notification` · `list_call_notification` | -| `client.api20100401_call_recording` | `Api20100401CallRecording` / `AsyncApi20100401CallRecording` | 5 | `create_call_recording` · `delete_call_recording` · `fetch_call_recording` · `list_call_recording` · `update_call_recording` | -| `client.api20100401_call_transcription` | `Api20100401CallTranscription` / `AsyncApi20100401CallTranscription` | 2 | `create_realtime_transcription` · `update_realtime_transcription` | -| `client.api20100401_conference` | `Api20100401Conference` / `AsyncApi20100401Conference` | 3 | `fetch_conference` · `list_conference` · `update_conference` | -| `client.api20100401_conference_recording` | `Api20100401ConferenceRecording` / `AsyncApi20100401ConferenceRecording` | 4 | `delete_conference_recording` · `fetch_conference_recording` · `list_conference_recording` · `update_conference_recording` | -| `client.api20100401_connect_app` | `Api20100401ConnectApp` / `AsyncApi20100401ConnectApp` | 4 | `delete_connect_app` · `fetch_connect_app` · `list_connect_app` · `update_connect_app` | -| `client.api20100401_credential` | `Api20100401Credential` / `AsyncApi20100401Credential` | 5 | `create_sip_credential` · `delete_sip_credential` · `fetch_sip_credential` · `list_sip_credential` · `update_sip_credential` | -| `client.api20100401_credential_list` | `Api20100401CredentialList` / `AsyncApi20100401CredentialList` | 5 | `create_sip_credential_list` · `delete_sip_credential_list` · `fetch_sip_credential_list` · `list_sip_credential_list` · `update_sip_credential_list` | -| `client.api20100401_credential_list_mapping` | `Api20100401CredentialListMapping` / `AsyncApi20100401CredentialListMapping` | 4 | `create_sip_credential_list_mapping` · `delete_sip_credential_list_mapping` · `fetch_sip_credential_list_mapping` · `list_sip_credential_list_mapping` | -| `client.api20100401_daily` | `Api20100401Daily` / `AsyncApi20100401Daily` | 1 | `list_usage_record_daily` | -| `client.api20100401_data` | `Api20100401Data` / `AsyncApi20100401Data` | 1 | `fetch_recording_add_on_result_payload_data` | -| `client.api20100401_dependent_phone_number` | `Api20100401DependentPhoneNumber` / `AsyncApi20100401DependentPhoneNumber` | 1 | `list_dependent_phone_number` | -| `client.api20100401_domain` | `Api20100401Domain` / `AsyncApi20100401Domain` | 5 | `create_sip_domain` · `delete_sip_domain` · `fetch_sip_domain` · `list_sip_domain` · `update_sip_domain` | -| `client.api20100401_event` | `Api20100401Event` / `AsyncApi20100401Event` | 1 | `list_call_event` | -| `client.api20100401_feedback` | `Api20100401Feedback` / `AsyncApi20100401Feedback` | 1 | `create_message_feedback` | -| `client.api20100401_incoming_phone_number` | `Api20100401IncomingPhoneNumber` / `AsyncApi20100401IncomingPhoneNumber` | 5 | `create_incoming_phone_number` · `delete_incoming_phone_number` · `fetch_incoming_phone_number` · `list_incoming_phone_number` · `update_incoming_phone_number` | -| `client.api20100401_incoming_phone_number_local` | `Api20100401IncomingPhoneNumberLocal` / `AsyncApi20100401IncomingPhoneNumberLocal` | 2 | `create_incoming_phone_number_local` · `list_incoming_phone_number_local` | -| `client.api20100401_incoming_phone_number_mobile` | `Api20100401IncomingPhoneNumberMobile` / `AsyncApi20100401IncomingPhoneNumberMobile` | 2 | `create_incoming_phone_number_mobile` · `list_incoming_phone_number_mobile` | -| `client.api20100401_incoming_phone_number_toll_free` | `Api20100401IncomingPhoneNumberTollFree` / `AsyncApi20100401IncomingPhoneNumberTollFree` | 2 | `create_incoming_phone_number_toll_free` · `list_incoming_phone_number_toll_free` | -| `client.api20100401_ip_access_control_list` | `Api20100401IpAccessControlList` / `AsyncApi20100401IpAccessControlList` | 5 | `create_sip_ip_access_control_list` · `delete_sip_ip_access_control_list` · `fetch_sip_ip_access_control_list` · `list_sip_ip_access_control_list` · `update_sip_ip_access_control_list` | -| `client.api20100401_ip_access_control_list_mapping` | `Api20100401IpAccessControlListMapping` / `AsyncApi20100401IpAccessControlListMapping` | 4 | `create_sip_ip_access_control_list_mapping` · `delete_sip_ip_access_control_list_mapping` · `fetch_sip_ip_access_control_list_mapping` · `list_sip_ip_access_control_list_mapping` | -| `client.api20100401_key` | `Api20100401Key` / `AsyncApi20100401Key` | 4 | `delete_key` · `fetch_key` · `list_key` · `update_key` | -| `client.api20100401_last_month` | `Api20100401LastMonth` / `AsyncApi20100401LastMonth` | 1 | `list_usage_record_last_month` | -| `client.api20100401_local` | `Api20100401Local` / `AsyncApi20100401Local` | 1 | `list_available_phone_number_local` | -| `client.api20100401_machine_to_machine` | `Api20100401MachineToMachine` / `AsyncApi20100401MachineToMachine` | 1 | `list_available_phone_number_machine_to_machine` | -| `client.api20100401_media` | `Api20100401Media` / `AsyncApi20100401Media` | 1 | `list_media` | -| `client.api20100401_media_instance` | `Api20100401MediaInstance` / `AsyncApi20100401MediaInstance` | 2 | `delete_media` · `fetch_media` | -| `client.api20100401_member` | `Api20100401Member` / `AsyncApi20100401Member` | 3 | `fetch_member` · `list_member` · `update_member` | -| `client.api20100401_message` | `Api20100401Message` / `AsyncApi20100401Message` | 5 | `create_message` · `delete_message` · `fetch_message` · `list_message` · `update_message` | -| `client.api20100401_mobile` | `Api20100401Mobile` / `AsyncApi20100401Mobile` | 1 | `list_available_phone_number_mobile` | -| `client.api20100401_monthly` | `Api20100401Monthly` / `AsyncApi20100401Monthly` | 1 | `list_usage_record_monthly` | -| `client.api20100401_national` | `Api20100401National` / `AsyncApi20100401National` | 1 | `list_available_phone_number_national` | -| `client.api20100401_new_key` | `Api20100401NewKey` / `AsyncApi20100401NewKey` | 1 | `create_new_key` | -| `client.api20100401_new_signing_key` | `Api20100401NewSigningKey` / `AsyncApi20100401NewSigningKey` | 1 | `create_new_signing_key` | -| `client.api20100401_notification` | `Api20100401Notification` / `AsyncApi20100401Notification` | 2 | `fetch_notification` · `list_notification` | -| `client.api20100401_outgoing_caller_id` | `Api20100401OutgoingCallerId` / `AsyncApi20100401OutgoingCallerId` | 4 | `delete_outgoing_caller_id` · `fetch_outgoing_caller_id` · `list_outgoing_caller_id` · `update_outgoing_caller_id` | -| `client.api20100401_participant` | `Api20100401Participant` / `AsyncApi20100401Participant` | 5 | `create_participant` · `delete_participant` · `fetch_participant` · `list_participant` · `update_participant` | -| `client.api20100401_payload` | `Api20100401Payload` / `AsyncApi20100401Payload` | 3 | `delete_recording_add_on_result_payload` · `fetch_recording_add_on_result_payload` · `list_recording_add_on_result_payload` | -| `client.api20100401_payment` | `Api20100401Payment` / `AsyncApi20100401Payment` | 2 | `create_payments` · `update_payments` | -| `client.api20100401_queue` | `Api20100401Queue` / `AsyncApi20100401Queue` | 5 | `create_queue` · `delete_queue` · `fetch_queue` · `list_queue` · `update_queue` | -| `client.api20100401_record` | `Api20100401Record` / `AsyncApi20100401Record` | 1 | `list_usage_record` | -| `client.api20100401_recording` | `Api20100401Recording` / `AsyncApi20100401Recording` | 3 | `delete_recording` · `fetch_recording` · `list_recording` | -| `client.api20100401_recording_transcription` | `Api20100401RecordingTranscription` / `AsyncApi20100401RecordingTranscription` | 3 | `delete_recording_transcription` · `fetch_recording_transcription` · `list_recording_transcription` | -| `client.api20100401_shared_cost` | `Api20100401SharedCost` / `AsyncApi20100401SharedCost` | 1 | `list_available_phone_number_shared_cost` | -| `client.api20100401_short_code` | `Api20100401ShortCode` / `AsyncApi20100401ShortCode` | 3 | `fetch_short_code` · `list_short_code` · `update_short_code` | -| `client.api20100401_signing_key` | `Api20100401SigningKey` / `AsyncApi20100401SigningKey` | 4 | `delete_signing_key` · `fetch_signing_key` · `list_signing_key` · `update_signing_key` | -| `client.api20100401_sip_ip_address` | `Api20100401SipIpAddress` / `AsyncApi20100401SipIpAddress` | 5 | `create_sip_ip_address` · `delete_sip_ip_address` · `fetch_sip_ip_address` · `list_sip_ip_address` · `update_sip_ip_address` | -| `client.api20100401_siprec` | `Api20100401Siprec` / `AsyncApi20100401Siprec` | 2 | `create_siprec` · `update_siprec` | -| `client.api20100401_stream` | `Api20100401Stream` / `AsyncApi20100401Stream` | 2 | `create_stream` · `update_stream` | -| `client.api20100401_this_month` | `Api20100401ThisMonth` / `AsyncApi20100401ThisMonth` | 1 | `list_usage_record_this_month` | -| `client.api20100401_today` | `Api20100401Today` / `AsyncApi20100401Today` | 1 | `list_usage_record_today` | -| `client.api20100401_token` | `Api20100401Token` / `AsyncApi20100401Token` | 1 | `create_token` | -| `client.api20100401_toll_free` | `Api20100401TollFree` / `AsyncApi20100401TollFree` | 1 | `list_available_phone_number_toll_free` | -| `client.api20100401_transcription` | `Api20100401Transcription` / `AsyncApi20100401Transcription` | 3 | `delete_transcription` · `fetch_transcription` · `list_transcription` | -| `client.api20100401_trigger` | `Api20100401Trigger` / `AsyncApi20100401Trigger` | 5 | `create_usage_trigger` · `delete_usage_trigger` · `fetch_usage_trigger` · `list_usage_trigger` · `update_usage_trigger` | -| `client.api20100401_user_defined_message` | `Api20100401UserDefinedMessage` / `AsyncApi20100401UserDefinedMessage` | 1 | `create_user_defined_message` | -| `client.api20100401_user_defined_message_subscription` | `Api20100401UserDefinedMessageSubscription` / `AsyncApi20100401UserDefinedMessageSubscription` | 2 | `create_user_defined_message_subscription` · `delete_user_defined_message_subscription` | -| `client.api20100401_validation_request` | `Api20100401ValidationRequest` / `AsyncApi20100401ValidationRequest` | 1 | `create_validation_request` | -| `client.api20100401_voip` | `Api20100401Voip` / `AsyncApi20100401Voip` | 1 | `list_available_phone_number_voip` | -| `client.api20100401_yearly` | `Api20100401Yearly` / `AsyncApi20100401Yearly` | 1 | `list_usage_record_yearly` | -| `client.api20100401_yesterday` | `Api20100401Yesterday` / `AsyncApi20100401Yesterday` | 1 | `list_usage_record_yesterday` | -| `client.content_v2_content` | `ContentV2Content` / `AsyncContentV2Content` | 1 | `list_content2` | -| `client.content_v2_content_and_approvals` | `ContentV2ContentAndApprovals` / `AsyncContentV2ContentAndApprovals` | 1 | `list_content_and_approvals2` | -| `client.contentv1_approval_create` | `Contentv1ApprovalCreate` / `AsyncContentv1ApprovalCreate` | 1 | `create_approval_create` | -| `client.contentv1_approval_fetch` | `Contentv1ApprovalFetch` / `AsyncContentv1ApprovalFetch` | 1 | `fetch_approval_fetch` | -| `client.contentv1_content_api` | `Contentv1ContentApi` / `AsyncContentv1ContentApi` | 5 | `create_content` · `delete_content` · `fetch_content` · `list_content` · `update_content` | -| `client.contentv1_content_and_approvals_api` | `Contentv1ContentAndApprovalsApi` / `AsyncContentv1ContentAndApprovalsApi` | 1 | `list_content_and_approvals` | -| `client.contentv1_legacy_content_api` | `Contentv1LegacyContentApi` / `AsyncContentv1LegacyContentApi` | 1 | `list_legacy_content` | -| `client.conversations_v1_address_configuration` | `ConversationsV1AddressConfiguration` / `AsyncConversationsV1AddressConfiguration` | 5 | `create_configuration_address` · `delete_configuration_address` · `fetch_configuration_address` · `list_configuration_address` · `update_configuration_address` | -| `client.conversations_v1_binding` | `ConversationsV1Binding` / `AsyncConversationsV1Binding` | 3 | `delete_service_binding` · `fetch_service_binding` · `list_service_binding` | -| `client.conversations_v1_configuration_api` | `ConversationsV1ConfigurationApi` / `AsyncConversationsV1ConfigurationApi` | 4 | `fetch_configuration` · `fetch_service_configuration` · `update_configuration` · `update_service_configuration` | -| `client.conversations_v1_conversation_api` | `ConversationsV1ConversationApi` / `AsyncConversationsV1ConversationApi` | 10 | `create_conversation` · `create_service_conversation` · `delete_conversation` · `delete_service_conversation` · `fetch_conversation` · `fetch_service_conversation` · … | -| `client.conversations_v1_conversation_with_participants_api` | `ConversationsV1ConversationWithParticipantsApi` / `AsyncConversationsV1ConversationWithParticipantsApi` | 2 | `create_conversation_with_participants` · `create_service_conversation_with_participants` | -| `client.conversations_v1_credential_api` | `ConversationsV1CredentialApi` / `AsyncConversationsV1CredentialApi` | 5 | `create_credential` · `delete_credential` · `fetch_credential` · `list_credential` · `update_credential` | -| `client.conversations_v1_delivery_receipt` | `ConversationsV1DeliveryReceipt` / `AsyncConversationsV1DeliveryReceipt` | 4 | `fetch_conversation_message_receipt` · `fetch_service_conversation_message_receipt` · `list_conversation_message_receipt` · `list_service_conversation_message_receipt` | -| `client.conversations_v1_message` | `ConversationsV1Message` / `AsyncConversationsV1Message` | 10 | `create_conversation_message` · `create_service_conversation_message` · `delete_conversation_message` · `delete_service_conversation_message` · `fetch_conversation_message` · `fetch_service_conversation_message` · … | -| `client.conversations_v1_notification` | `ConversationsV1Notification` / `AsyncConversationsV1Notification` | 2 | `fetch_service_notification` · `update_service_notification` | -| `client.conversations_v1_participant` | `ConversationsV1Participant` / `AsyncConversationsV1Participant` | 10 | `create_conversation_participant` · `create_service_conversation_participant` · `delete_conversation_participant` · `delete_service_conversation_participant` · `fetch_conversation_participant` · `fetch_service_conversation_participant` · … | -| `client.conversations_v1_participant_conversation_api` | `ConversationsV1ParticipantConversationApi` / `AsyncConversationsV1ParticipantConversationApi` | 2 | `list_participant_conversation` · `list_service_participant_conversation` | -| `client.conversations_v1_role_api` | `ConversationsV1RoleApi` / `AsyncConversationsV1RoleApi` | 10 | `create_role` · `create_service_role` · `delete_role` · `delete_service_role` · `fetch_role` · `fetch_service_role` · … | -| `client.conversations_v1_service_api` | `ConversationsV1ServiceApi` / `AsyncConversationsV1ServiceApi` | 4 | `create_service3` · `delete_service3` · `fetch_service3` · `list_service3` | -| `client.conversations_v1_user_api` | `ConversationsV1UserApi` / `AsyncConversationsV1UserApi` | 10 | `create_service_user` · `create_user` · `delete_service_user` · `delete_user` · `fetch_service_user` · `fetch_user` · … | -| `client.conversations_v1_user_conversation` | `ConversationsV1UserConversation` / `AsyncConversationsV1UserConversation` | 8 | `delete_service_user_conversation` · `delete_user_conversation` · `fetch_service_user_conversation` · `fetch_user_conversation` · `list_service_user_conversation` · `list_user_conversation` · … | -| `client.conversations_v1_webhook` | `ConversationsV1Webhook` / `AsyncConversationsV1Webhook` | 14 | `create_conversation_scoped_webhook` · `create_service_conversation_scoped_webhook` · `delete_conversation_scoped_webhook` · `delete_service_conversation_scoped_webhook` · `fetch_configuration_webhook` · `fetch_conversation_scoped_webhook` · … | -| `client.conversations_v2_action_api` | `ConversationsV2ActionApi` / `AsyncConversationsV2ActionApi` | 2 | `create_conversation_action` · `fetch_conversation_action` | -| `client.conversations_v2_communication_api` | `ConversationsV2CommunicationApi` / `AsyncConversationsV2CommunicationApi` | 3 | `create_communication_in_conversation` · `fetch_communication` · `list_communication_by_conversation` | -| `client.conversations_v2_configuration_api` | `ConversationsV2ConfigurationApi` / `AsyncConversationsV2ConfigurationApi` | 5 | `create_configuration` · `delete_configuration` · `fetch_configuration2` · `list_configuration` · `update_configuration2` | -| `client.conversations_v2_conversation_api` | `ConversationsV2ConversationApi` / `AsyncConversationsV2ConversationApi` | 6 | `create_conversation_with_config` · `delete_conversation_async` · `fetch_conversation2` · `list_conversation_by_account` · `patch_conversation_by_id` · `update_conversation_by_id` | -| `client.conversations_v2_operation` | `ConversationsV2Operation` / `AsyncConversationsV2Operation` | 1 | `fetch_operation_status` | -| `client.conversations_v2_participant_api` | `ConversationsV2ParticipantApi` / `AsyncConversationsV2ParticipantApi` | 4 | `create_participant_in_conversation` · `fetch_participant2` · `list_participant_by_conversation` · `update_participant_in_conversation` | -| `client.flex_v1_assessments` | `FlexV1Assessments` / `AsyncFlexV1Assessments` | 3 | `create_insights_assessments` · `list_insights_assessments` · `update_insights_assessments` | -| `client.flex_v1_channel_api` | `FlexV1ChannelApi` / `AsyncFlexV1ChannelApi` | 4 | `create_channel` · `delete_channel` · `fetch_channel` · `list_channel` | -| `client.flex_v1_configuration_api` | `FlexV1ConfigurationApi` / `AsyncFlexV1ConfigurationApi` | 2 | `fetch_configuration3` · `update_configuration3` | -| `client.flex_v1_configured_plugin` | `FlexV1ConfiguredPlugin` / `AsyncFlexV1ConfiguredPlugin` | 2 | `fetch_configured_plugin` · `list_configured_plugin` | -| `client.flex_v1_flex_flow_api` | `FlexV1FlexFlowApi` / `AsyncFlexV1FlexFlowApi` | 5 | `create_flex_flow` · `delete_flex_flow` · `fetch_flex_flow` · `list_flex_flow` · `update_flex_flow` | -| `client.flex_v1_insights_assessments_comment_api` | `FlexV1InsightsAssessmentsCommentApi` / `AsyncFlexV1InsightsAssessmentsCommentApi` | 2 | `create_insights_assessments_comment` · `list_insights_assessments_comment` | -| `client.flex_v1_insights_conversations_api` | `FlexV1InsightsConversationsApi` / `AsyncFlexV1InsightsConversationsApi` | 1 | `list_insights_conversations` | -| `client.flex_v1_insights_questionnaires_api` | `FlexV1InsightsQuestionnairesApi` / `AsyncFlexV1InsightsQuestionnairesApi` | 5 | `create_insights_questionnaires` · `delete_insights_questionnaires` · `fetch_insights_questionnaires` · `list_insights_questionnaires` · `update_insights_questionnaires` | -| `client.flex_v1_insights_questionnaires_category_api` | `FlexV1InsightsQuestionnairesCategoryApi` / `AsyncFlexV1InsightsQuestionnairesCategoryApi` | 4 | `create_insights_questionnaires_category` · `delete_insights_questionnaires_category` · `list_insights_questionnaires_category` · `update_insights_questionnaires_category` | -| `client.flex_v1_insights_questionnaires_question_api` | `FlexV1InsightsQuestionnairesQuestionApi` / `AsyncFlexV1InsightsQuestionnairesQuestionApi` | 4 | `create_insights_questionnaires_question` · `delete_insights_questionnaires_question` · `list_insights_questionnaires_question` · `update_insights_questionnaires_question` | -| `client.flex_v1_insights_segments_api` | `FlexV1InsightsSegmentsApi` / `AsyncFlexV1InsightsSegmentsApi` | 1 | `list_insights_segments` | -| `client.flex_v1_insights_session_api` | `FlexV1InsightsSessionApi` / `AsyncFlexV1InsightsSessionApi` | 1 | `create_insights_session` | -| `client.flex_v1_insights_settings_answer_sets_api` | `FlexV1InsightsSettingsAnswerSetsApi` / `AsyncFlexV1InsightsSettingsAnswerSetsApi` | 1 | `fetch_insights_settings_answersets` | -| `client.flex_v1_insights_settings_comment_api` | `FlexV1InsightsSettingsCommentApi` / `AsyncFlexV1InsightsSettingsCommentApi` | 1 | `fetch_insights_settings_comment` | -| `client.flex_v1_insights_user_roles_api` | `FlexV1InsightsUserRolesApi` / `AsyncFlexV1InsightsUserRolesApi` | 1 | `fetch_insights_user_roles` | -| `client.flex_v1_interaction_api` | `FlexV1InteractionApi` / `AsyncFlexV1InteractionApi` | 3 | `create_interaction` · `fetch_interaction2` · `update_interaction` | -| `client.flex_v1_interaction_channel` | `FlexV1InteractionChannel` / `AsyncFlexV1InteractionChannel` | 3 | `fetch_interaction_channel` · `list_interaction_channel` · `update_interaction_channel` | -| `client.flex_v1_interaction_channel_invite` | `FlexV1InteractionChannelInvite` / `AsyncFlexV1InteractionChannelInvite` | 2 | `create_interaction_channel_invite` · `list_interaction_channel_invite` | -| `client.flex_v1_interaction_channel_participant` | `FlexV1InteractionChannelParticipant` / `AsyncFlexV1InteractionChannelParticipant` | 3 | `create_interaction_channel_participant` · `list_interaction_channel_participant` · `update_interaction_channel_participant` | -| `client.flex_v1_interaction_transfer` | `FlexV1InteractionTransfer` / `AsyncFlexV1InteractionTransfer` | 3 | `create_interaction_transfer` · `fetch_interaction_transfer` · `update_interaction_transfer` | -| `client.flex_v1_plugin_api` | `FlexV1PluginApi` / `AsyncFlexV1PluginApi` | 4 | `create_plugin` · `fetch_plugin` · `list_plugin` · `update_plugin` | -| `client.flex_v1_plugin_archive_api` | `FlexV1PluginArchiveApi` / `AsyncFlexV1PluginArchiveApi` | 1 | `update_plugin_archive` | -| `client.flex_v1_plugin_configuration_api` | `FlexV1PluginConfigurationApi` / `AsyncFlexV1PluginConfigurationApi` | 3 | `create_plugin_configuration` · `fetch_plugin_configuration` · `list_plugin_configuration` | -| `client.flex_v1_plugin_configuration_archive_api` | `FlexV1PluginConfigurationArchiveApi` / `AsyncFlexV1PluginConfigurationArchiveApi` | 1 | `update_plugin_configuration_archive` | -| `client.flex_v1_plugin_release_api` | `FlexV1PluginReleaseApi` / `AsyncFlexV1PluginReleaseApi` | 3 | `create_plugin_release` · `fetch_plugin_release` · `list_plugin_release` | -| `client.flex_v1_plugin_version_archive_api` | `FlexV1PluginVersionArchiveApi` / `AsyncFlexV1PluginVersionArchiveApi` | 1 | `update_plugin_version_archive` | -| `client.flex_v1_plugin_versions` | `FlexV1PluginVersions` / `AsyncFlexV1PluginVersions` | 3 | `create_plugin_version` · `fetch_plugin_version` · `list_plugin_version` | -| `client.flex_v1_provisioning_status_api` | `FlexV1ProvisioningStatusApi` / `AsyncFlexV1ProvisioningStatusApi` | 1 | `fetch_provisioning_status` | -| `client.flex_v1_web_channel_api` | `FlexV1WebChannelApi` / `AsyncFlexV1WebChannelApi` | 5 | `create_web_channel` · `delete_web_channel` · `fetch_web_channel` · `list_web_channel` · `update_web_channel` | -| `client.flex_v2_flex_user_api` | `FlexV2FlexUserApi` / `AsyncFlexV2FlexUserApi` | 2 | `fetch_flex_user` · `update_flex_user` | -| `client.flex_v2_web_channels` | `FlexV2WebChannels` / `AsyncFlexV2WebChannels` | 1 | `create_web_channel2` | -| `client.insights_v1_annotation` | `InsightsV1Annotation` / `AsyncInsightsV1Annotation` | 2 | `fetch_annotation` · `update_annotation` | -| `client.insights_v1_call_api` | `InsightsV1CallApi` / `AsyncInsightsV1CallApi` | 1 | `fetch_call2` | -| `client.insights_v1_call_summaries_api` | `InsightsV1CallSummariesApi` / `AsyncInsightsV1CallSummariesApi` | 1 | `list_call_summaries` | -| `client.insights_v1_call_summary_api` | `InsightsV1CallSummaryApi` / `AsyncInsightsV1CallSummaryApi` | 1 | `fetch_summary` | -| `client.insights_v1_conference_api` | `InsightsV1ConferenceApi` / `AsyncInsightsV1ConferenceApi` | 2 | `fetch_conference2` · `list_conference2` | -| `client.insights_v1_conference_participant` | `InsightsV1ConferenceParticipant` / `AsyncInsightsV1ConferenceParticipant` | 2 | `fetch_conference_participant` · `list_conference_participant` | -| `client.insights_v1_create_account_report` | `InsightsV1CreateAccountReport` / `AsyncInsightsV1CreateAccountReport` | 1 | `create_account_report` | -| `client.insights_v1_create_inbound_phone_numbers_report` | `InsightsV1CreateInboundPhoneNumbersReport` / `AsyncInsightsV1CreateInboundPhoneNumbersReport` | 1 | `create_inbound_phone_numbers_report` | -| `client.insights_v1_create_outbound_phone_numbers_report` | `InsightsV1CreateOutboundPhoneNumbersReport` / `AsyncInsightsV1CreateOutboundPhoneNumbersReport` | 1 | `create_outbound_phone_numbers_report` | -| `client.insights_v1_event` | `InsightsV1Event` / `AsyncInsightsV1Event` | 1 | `list_event2` | -| `client.insights_v1_get_account_report` | `InsightsV1GetAccountReport` / `AsyncInsightsV1GetAccountReport` | 1 | `fetch_account_report` | -| `client.insights_v1_get_inbound_phone_numbers_report` | `InsightsV1GetInboundPhoneNumbersReport` / `AsyncInsightsV1GetInboundPhoneNumbersReport` | 1 | `list_inbound_phone_numbers_report` | -| `client.insights_v1_get_outbound_phone_numbers_report` | `InsightsV1GetOutboundPhoneNumbersReport` / `AsyncInsightsV1GetOutboundPhoneNumbersReport` | 1 | `list_outbound_phone_numbers_report` | -| `client.insights_v1_metric` | `InsightsV1Metric` / `AsyncInsightsV1Metric` | 1 | `list_metric` | -| `client.insights_v1_participant` | `InsightsV1Participant` / `AsyncInsightsV1Participant` | 2 | `fetch_video_participant_summary` · `list_video_participant_summary` | -| `client.insights_v1_room` | `InsightsV1Room` / `AsyncInsightsV1Room` | 2 | `fetch_video_room_summary` · `list_video_room_summary` | -| `client.insights_v1_setting` | `InsightsV1Setting` / `AsyncInsightsV1Setting` | 2 | `fetch_account_settings` · `update_account_settings` | -| `client.lookups_v1_phone_number_api` | `LookupsV1PhoneNumberApi` / `AsyncLookupsV1PhoneNumberApi` | 1 | `fetch_phone_number2` | -| `client.lookups_v2_phone_number` | `LookupsV2PhoneNumber` / `AsyncLookupsV2PhoneNumber` | 1 | `fetch_phone_number3` | -| `client.messaging_v1_alpha_sender` | `MessagingV1AlphaSender` / `AsyncMessagingV1AlphaSender` | 4 | `create_alpha_sender` · `delete_alpha_sender` · `fetch_alpha_sender` · `list_alpha_sender` | -| `client.messaging_v1_brand_registration` | `MessagingV1BrandRegistration` / `AsyncMessagingV1BrandRegistration` | 4 | `create_brand_registrations` · `fetch_brand_registrations` · `list_brand_registrations` · `update_brand_registrations` | -| `client.messaging_v1_brand_registration_otp` | `MessagingV1BrandRegistrationOtp` / `AsyncMessagingV1BrandRegistrationOtp` | 1 | `create_brand_registration_otp` | -| `client.messaging_v1_brand_vetting` | `MessagingV1BrandVetting` / `AsyncMessagingV1BrandVetting` | 3 | `create_brand_vetting` · `fetch_brand_vetting` · `list_brand_vetting` | -| `client.messaging_v1_channel_sender` | `MessagingV1ChannelSender` / `AsyncMessagingV1ChannelSender` | 4 | `create_channel_sender` · `delete_channel_sender` · `fetch_channel_sender` · `list_channel_sender` | -| `client.messaging_v1_deactivations` | `MessagingV1Deactivations` / `AsyncMessagingV1Deactivations` | 1 | `fetch_deactivation` | -| `client.messaging_v1_destination_alpha_sender` | `MessagingV1DestinationAlphaSender` / `AsyncMessagingV1DestinationAlphaSender` | 4 | `create_destination_alpha_sender` · `delete_destination_alpha_sender` · `fetch_destination_alpha_sender` · `list_destination_alpha_sender` | -| `client.messaging_v1_domain_certs` | `MessagingV1DomainCerts` / `AsyncMessagingV1DomainCerts` | 3 | `delete_domain_cert_v4` · `fetch_domain_cert_v4` · `update_domain_cert_v4` | -| `client.messaging_v1_domain_config_api` | `MessagingV1DomainConfigApi` / `AsyncMessagingV1DomainConfigApi` | 2 | `fetch_domain_config` · `update_domain_config` | -| `client.messaging_v1_domain_config_messaging_service_api` | `MessagingV1DomainConfigMessagingServiceApi` / `AsyncMessagingV1DomainConfigMessagingServiceApi` | 1 | `fetch_domain_config_messaging_service` | -| `client.messaging_v1_domain_validate_dns` | `MessagingV1DomainValidateDns` / `AsyncMessagingV1DomainValidateDns` | 1 | `fetch_domain_dns_validation` | -| `client.messaging_v1_external_campaign_api` | `MessagingV1ExternalCampaignApi` / `AsyncMessagingV1ExternalCampaignApi` | 1 | `create_external_campaign` | -| `client.messaging_v1_linkshortening_messaging_service_api` | `MessagingV1LinkshorteningMessagingServiceApi` / `AsyncMessagingV1LinkshorteningMessagingServiceApi` | 2 | `create_linkshortening_messaging_service` · `delete_linkshortening_messaging_service` | -| `client.messaging_v1_linkshortening_messaging_service_domain_association_api` | `MessagingV1LinkshorteningMessagingServiceDomainAssociationApi` / `AsyncMessagingV1LinkshorteningMessagingServiceDomainAssociationApi` | 1 | `fetch_linkshortening_messaging_service_domain_association` | -| `client.messaging_v1_phone_number` | `MessagingV1PhoneNumber` / `AsyncMessagingV1PhoneNumber` | 4 | `create_phone_number` · `delete_phone_number` · `fetch_phone_number` · `list_phone_number` | -| `client.messaging_v1_request_managed_cert_api` | `MessagingV1RequestManagedCertApi` / `AsyncMessagingV1RequestManagedCertApi` | 1 | `update_request_managed_cert` | -| `client.messaging_v1_service_api` | `MessagingV1ServiceApi` / `AsyncMessagingV1ServiceApi` | 5 | `create_service` · `delete_service` · `fetch_service` · `list_service` · `update_service` | -| `client.messaging_v1_short_code` | `MessagingV1ShortCode` / `AsyncMessagingV1ShortCode` | 4 | `create_short_code` · `delete_short_code` · `fetch_short_code2` · `list_short_code2` | -| `client.messaging_v1_tollfree_verification_api` | `MessagingV1TollfreeVerificationApi` / `AsyncMessagingV1TollfreeVerificationApi` | 5 | `create_tollfree_verification` · `delete_tollfree_verification` · `fetch_tollfree_verification` · `list_tollfree_verification` · `update_tollfree_verification` | -| `client.messaging_v1_us_app_to_person` | `MessagingV1UsAppToPerson` / `AsyncMessagingV1UsAppToPerson` | 5 | `create_us_app_to_person` · `delete_us_app_to_person` · `fetch_us_app_to_person` · `list_us_app_to_person` · `update_us_app_to_person` | -| `client.messaging_v1_us_app_to_person_usecase` | `MessagingV1UsAppToPersonUsecase` / `AsyncMessagingV1UsAppToPersonUsecase` | 1 | `fetch_us_app_to_person_usecase` | -| `client.messaging_v1_usecase_api` | `MessagingV1UsecaseApi` / `AsyncMessagingV1UsecaseApi` | 1 | `fetch_usecase` | -| `client.messaging_v2_channels_sender` | `MessagingV2ChannelsSender` / `AsyncMessagingV2ChannelsSender` | 5 | `create_channels_sender` · `delete_channels_sender` · `fetch_channels_sender` · `list_channels_sender` · `update_channels_sender` | -| `client.messaging_v2_domain_certs` | `MessagingV2DomainCerts` / `AsyncMessagingV2DomainCerts` | 1 | `fetch_domain_cert_v42` | -| `client.messaging_v2_typing_indicator` | `MessagingV2TypingIndicator` / `AsyncMessagingV2TypingIndicator` | 1 | `create_typing_indicator` | -| `client.messaging_v3_typing_indicator` | `MessagingV3TypingIndicator` / `AsyncMessagingV3TypingIndicator` | 1 | `create_v3_typing_indicator` | -| `client.numbers_v1_bulk_eligibility_api` | `NumbersV1BulkEligibilityApi` / `AsyncNumbersV1BulkEligibilityApi` | 2 | `create_bulk_eligibility` · `fetch_bulk_eligibility` | -| `client.numbers_v1_eligibility_api` | `NumbersV1EligibilityApi` / `AsyncNumbersV1EligibilityApi` | 1 | `create_eligibility` | -| `client.numbers_v1_porting_port_in_api` | `NumbersV1PortingPortInApi` / `AsyncNumbersV1PortingPortInApi` | 4 | `create_porting_port_in` · `delete_porting_port_in` · `fetch_porting_port_in` · `list_port_in_requests` | -| `client.numbers_v1_porting_port_in_phone_number_api` | `NumbersV1PortingPortInPhoneNumberApi` / `AsyncNumbersV1PortingPortInPhoneNumberApi` | 2 | `delete_porting_port_in_phone_number` · `fetch_porting_port_in_phone_number` | -| `client.numbers_v1_porting_portability_api` | `NumbersV1PortingPortabilityApi` / `AsyncNumbersV1PortingPortabilityApi` | 1 | `fetch_porting_portability` | -| `client.numbers_v1_porting_webhook_configuration_api` | `NumbersV1PortingWebhookConfigurationApi` / `AsyncNumbersV1PortingWebhookConfigurationApi` | 1 | `create_porting_webhook_configuration` | -| `client.numbers_v1_porting_webhook_configuration_delete_api` | `NumbersV1PortingWebhookConfigurationDeleteApi` / `AsyncNumbersV1PortingWebhookConfigurationDeleteApi` | 1 | `delete_porting_webhook_configuration_delete` | -| `client.numbers_v1_porting_webhook_configuration_fetch_api` | `NumbersV1PortingWebhookConfigurationFetchApi` / `AsyncNumbersV1PortingWebhookConfigurationFetchApi` | 1 | `fetch_porting_webhook_configuration_fetch` | -| `client.numbers_v1_sender_id_registration` | `NumbersV1SenderIdRegistration` / `AsyncNumbersV1SenderIdRegistration` | 1 | `create_sender_id_registration` | -| `client.numbers_v1_sender_id_registration_embedded_session` | `NumbersV1SenderIdRegistrationEmbeddedSession` / `AsyncNumbersV1SenderIdRegistrationEmbeddedSession` | 1 | `create_sender_id_registration_embedded_session` | -| `client.numbers_v1_signing_request_configuration_api` | `NumbersV1SigningRequestConfigurationApi` / `AsyncNumbersV1SigningRequestConfigurationApi` | 2 | `create_signing_request_configuration` · `list_signing_request_configuration` | -| `client.numbers_v2_authorization_document_api` | `NumbersV2AuthorizationDocumentApi` / `AsyncNumbersV2AuthorizationDocumentApi` | 4 | `create_authorization_document` · `delete_authorization_document` · `fetch_authorization_document` · `list_authorization_document` | -| `client.numbers_v2_bulk_hosted_number_order_api` | `NumbersV2BulkHostedNumberOrderApi` / `AsyncNumbersV2BulkHostedNumberOrderApi` | 2 | `create_bulk_hosted_number_order` · `fetch_bulk_hosted_number_order` | -| `client.numbers_v2_bundle` | `NumbersV2Bundle` / `AsyncNumbersV2Bundle` | 5 | `create_bundle` · `delete_bundle` · `fetch_bundle` · `list_bundle` · `update_bundle` | -| `client.numbers_v2_bundle_clone_api` | `NumbersV2BundleCloneApi` / `AsyncNumbersV2BundleCloneApi` | 1 | `create_bundle_clone` | -| `client.numbers_v2_bundle_copy` | `NumbersV2BundleCopy` / `AsyncNumbersV2BundleCopy` | 2 | `create_bundle_copy` · `list_bundle_copy` | -| `client.numbers_v2_dependent_hosted_number_order` | `NumbersV2DependentHostedNumberOrder` / `AsyncNumbersV2DependentHostedNumberOrder` | 1 | `list_dependent_hosted_number_order` | -| `client.numbers_v2_end_user` | `NumbersV2EndUser` / `AsyncNumbersV2EndUser` | 5 | `create_end_user` · `delete_end_user` · `fetch_end_user` · `list_end_user` · `update_end_user` | -| `client.numbers_v2_end_user_type` | `NumbersV2EndUserType` / `AsyncNumbersV2EndUserType` | 2 | `fetch_end_user_type` · `list_end_user_type` | -| `client.numbers_v2_evaluation` | `NumbersV2Evaluation` / `AsyncNumbersV2Evaluation` | 3 | `create_evaluation` · `fetch_evaluation` · `list_evaluation` | -| `client.numbers_v2_hosted_number_order_api` | `NumbersV2HostedNumberOrderApi` / `AsyncNumbersV2HostedNumberOrderApi` | 5 | `create_hosted_number_order` · `delete_hosted_number_order` · `fetch_hosted_number_order` · `list_hosted_number_order` · `update_hosted_number_order` | -| `client.numbers_v2_item_assignment` | `NumbersV2ItemAssignment` / `AsyncNumbersV2ItemAssignment` | 4 | `create_item_assignment` · `delete_item_assignment` · `fetch_item_assignment` · `list_item_assignment` | -| `client.numbers_v2_regulation` | `NumbersV2Regulation` / `AsyncNumbersV2Regulation` | 2 | `fetch_regulation` · `list_regulation` | -| `client.numbers_v2_replace_items` | `NumbersV2ReplaceItems` / `AsyncNumbersV2ReplaceItems` | 1 | `create_replace_items` | -| `client.numbers_v2_supporting_document` | `NumbersV2SupportingDocument` / `AsyncNumbersV2SupportingDocument` | 5 | `create_supporting_document` · `delete_supporting_document` · `fetch_supporting_document` · `list_supporting_document` · `update_supporting_document` | -| `client.numbers_v2_supporting_document_type` | `NumbersV2SupportingDocumentType` / `AsyncNumbersV2SupportingDocumentType` | 2 | `fetch_supporting_document_type` · `list_supporting_document_type` | -| `client.numbers_v3_hosted_numbers_hosted_number_order_api` | `NumbersV3HostedNumbersHostedNumberOrderApi` / `AsyncNumbersV3HostedNumbersHostedNumberOrderApi` | 1 | `create_hosted_numbers_hosted_number_order` | -| `client.proxy_v1_interaction` | `ProxyV1Interaction` / `AsyncProxyV1Interaction` | 3 | `delete_interaction` · `fetch_interaction` · `list_interaction` | -| `client.proxy_v1_message_interaction` | `ProxyV1MessageInteraction` / `AsyncProxyV1MessageInteraction` | 3 | `create_message_interaction` · `fetch_message_interaction` · `list_message_interaction` | -| `client.proxy_v1_participant` | `ProxyV1Participant` / `AsyncProxyV1Participant` | 4 | `create_participant2` · `delete_participant2` · `fetch_participant3` · `list_participant2` | -| `client.proxy_v1_phone_number` | `ProxyV1PhoneNumber` / `AsyncProxyV1PhoneNumber` | 5 | `create_phone_number2` · `delete_phone_number2` · `fetch_phone_number4` · `list_phone_number2` · `update_phone_number` | -| `client.proxy_v1_service_api` | `ProxyV1ServiceApi` / `AsyncProxyV1ServiceApi` | 5 | `create_service4` · `delete_service4` · `fetch_service4` · `list_service4` · `update_service3` | -| `client.proxy_v1_session` | `ProxyV1Session` / `AsyncProxyV1Session` | 5 | `create_session` · `delete_session` · `fetch_session` · `list_session` · `update_session` | -| `client.studio_v1_engagement` | `StudioV1Engagement` / `AsyncStudioV1Engagement` | 4 | `create_engagement` · `delete_engagement` · `fetch_engagement` · `list_engagement` | -| `client.studio_v1_engagement_context` | `StudioV1EngagementContext` / `AsyncStudioV1EngagementContext` | 1 | `fetch_engagement_context` | -| `client.studio_v1_execution` | `StudioV1Execution` / `AsyncStudioV1Execution` | 5 | `create_execution` · `delete_execution` · `fetch_execution` · `list_execution` · `update_execution` | -| `client.studio_v1_execution_context` | `StudioV1ExecutionContext` / `AsyncStudioV1ExecutionContext` | 1 | `fetch_execution_context` | -| `client.studio_v1_execution_step` | `StudioV1ExecutionStep` / `AsyncStudioV1ExecutionStep` | 2 | `fetch_execution_step` · `list_execution_step` | -| `client.studio_v1_execution_step_context` | `StudioV1ExecutionStepContext` / `AsyncStudioV1ExecutionStepContext` | 1 | `fetch_execution_step_context` | -| `client.studio_v1_flow_api` | `StudioV1FlowApi` / `AsyncStudioV1FlowApi` | 3 | `delete_flow` · `fetch_flow` · `list_flow` | -| `client.studio_v1_step` | `StudioV1Step` / `AsyncStudioV1Step` | 2 | `fetch_step` · `list_step` | -| `client.studio_v1_step_context` | `StudioV1StepContext` / `AsyncStudioV1StepContext` | 1 | `fetch_step_context` | -| `client.studio_v2_execution` | `StudioV2Execution` / `AsyncStudioV2Execution` | 5 | `create_execution2` · `delete_execution2` · `fetch_execution2` · `list_execution2` · `update_execution2` | -| `client.studio_v2_execution_context` | `StudioV2ExecutionContext` / `AsyncStudioV2ExecutionContext` | 1 | `fetch_execution_context2` | -| `client.studio_v2_execution_step` | `StudioV2ExecutionStep` / `AsyncStudioV2ExecutionStep` | 2 | `fetch_execution_step2` · `list_execution_step2` | -| `client.studio_v2_execution_step_context` | `StudioV2ExecutionStepContext` / `AsyncStudioV2ExecutionStepContext` | 1 | `fetch_execution_step_context2` | -| `client.studio_v2_flow_api` | `StudioV2FlowApi` / `AsyncStudioV2FlowApi` | 5 | `create_flow` · `delete_flow2` · `fetch_flow2` · `list_flow2` · `update_flow` | -| `client.studio_v2_flow_revision` | `StudioV2FlowRevision` / `AsyncStudioV2FlowRevision` | 2 | `fetch_flow_revision` · `list_flow_revision` | -| `client.studio_v2_flow_test_user_api` | `StudioV2FlowTestUserApi` / `AsyncStudioV2FlowTestUserApi` | 2 | `fetch_test_user` · `update_test_user` | -| `client.studio_v2_flow_validate_api` | `StudioV2FlowValidateApi` / `AsyncStudioV2FlowValidateApi` | 1 | `update_flow_validate` | -| `client.sync_v1_document` | `SyncV1Document` / `AsyncSyncV1Document` | 5 | `create_document` · `delete_document` · `fetch_document` · `list_document` · `update_document` | -| `client.sync_v1_document_permission` | `SyncV1DocumentPermission` / `AsyncSyncV1DocumentPermission` | 4 | `delete_document_permission` · `fetch_document_permission` · `list_document_permission` · `update_document_permission` | -| `client.sync_v1_service_api` | `SyncV1ServiceApi` / `AsyncSyncV1ServiceApi` | 5 | `create_service5` · `delete_service5` · `fetch_service5` · `list_service5` · `update_service4` | -| `client.sync_v1_stream_message` | `SyncV1StreamMessage` / `AsyncSyncV1StreamMessage` | 1 | `create_stream_message` | -| `client.sync_v1_sync_list` | `SyncV1SyncList` / `AsyncSyncV1SyncList` | 5 | `create_sync_list` · `delete_sync_list` · `fetch_sync_list` · `list_sync_list` · `update_sync_list` | -| `client.sync_v1_sync_list_item` | `SyncV1SyncListItem` / `AsyncSyncV1SyncListItem` | 5 | `create_sync_list_item` · `delete_sync_list_item` · `fetch_sync_list_item` · `list_sync_list_item` · `update_sync_list_item` | -| `client.sync_v1_sync_list_permission` | `SyncV1SyncListPermission` / `AsyncSyncV1SyncListPermission` | 4 | `delete_sync_list_permission` · `fetch_sync_list_permission` · `list_sync_list_permission` · `update_sync_list_permission` | -| `client.sync_v1_sync_map` | `SyncV1SyncMap` / `AsyncSyncV1SyncMap` | 5 | `create_sync_map` · `delete_sync_map` · `fetch_sync_map` · `list_sync_map` · `update_sync_map` | -| `client.sync_v1_sync_map_item` | `SyncV1SyncMapItem` / `AsyncSyncV1SyncMapItem` | 5 | `create_sync_map_item` · `delete_sync_map_item` · `fetch_sync_map_item` · `list_sync_map_item` · `update_sync_map_item` | -| `client.sync_v1_sync_map_permission` | `SyncV1SyncMapPermission` / `AsyncSyncV1SyncMapPermission` | 4 | `delete_sync_map_permission` · `fetch_sync_map_permission` · `list_sync_map_permission` · `update_sync_map_permission` | -| `client.sync_v1_sync_stream` | `SyncV1SyncStream` / `AsyncSyncV1SyncStream` | 5 | `create_sync_stream` · `delete_sync_stream` · `fetch_sync_stream` · `list_sync_stream` · `update_sync_stream` | -| `client.taskrouter_v1_activity` | `TaskrouterV1Activity` / `AsyncTaskrouterV1Activity` | 5 | `create_activity` · `delete_activity` · `fetch_activity` · `list_activity` · `update_activity` | -| `client.taskrouter_v1_event` | `TaskrouterV1Event` / `AsyncTaskrouterV1Event` | 2 | `fetch_event` · `list_event` | -| `client.taskrouter_v1_task` | `TaskrouterV1Task` / `AsyncTaskrouterV1Task` | 5 | `create_task` · `delete_task` · `fetch_task` · `list_task` · `update_task` | -| `client.taskrouter_v1_task_channel` | `TaskrouterV1TaskChannel` / `AsyncTaskrouterV1TaskChannel` | 5 | `create_task_channel` · `delete_task_channel` · `fetch_task_channel` · `list_task_channel` · `update_task_channel` | -| `client.taskrouter_v1_task_queue` | `TaskrouterV1TaskQueue` / `AsyncTaskrouterV1TaskQueue` | 5 | `create_task_queue` · `delete_task_queue` · `fetch_task_queue` · `list_task_queue` · `update_task_queue` | -| `client.taskrouter_v1_task_queue_bulk_real_time_statistics` | `TaskrouterV1TaskQueueBulkRealTimeStatistics` / `AsyncTaskrouterV1TaskQueueBulkRealTimeStatistics` | 1 | `create_task_queue_bulk_real_time_statistics` | -| `client.taskrouter_v1_task_queue_cumulative_statistics` | `TaskrouterV1TaskQueueCumulativeStatistics` / `AsyncTaskrouterV1TaskQueueCumulativeStatistics` | 1 | `fetch_task_queue_cumulative_statistics` | -| `client.taskrouter_v1_task_queue_real_time_statistics` | `TaskrouterV1TaskQueueRealTimeStatistics` / `AsyncTaskrouterV1TaskQueueRealTimeStatistics` | 1 | `fetch_task_queue_real_time_statistics` | -| `client.taskrouter_v1_task_queue_statistics` | `TaskrouterV1TaskQueueStatistics` / `AsyncTaskrouterV1TaskQueueStatistics` | 1 | `fetch_task_queue_statistics` | -| `client.taskrouter_v1_task_queues_statistics` | `TaskrouterV1TaskQueuesStatistics` / `AsyncTaskrouterV1TaskQueuesStatistics` | 1 | `list_task_queues_statistics` | -| `client.taskrouter_v1_task_reservation` | `TaskrouterV1TaskReservation` / `AsyncTaskrouterV1TaskReservation` | 3 | `fetch_task_reservation` · `list_task_reservation` · `update_task_reservation` | -| `client.taskrouter_v1_worker` | `TaskrouterV1Worker` / `AsyncTaskrouterV1Worker` | 5 | `create_worker` · `delete_worker` · `fetch_worker` · `list_worker` · `update_worker` | -| `client.taskrouter_v1_worker_channel` | `TaskrouterV1WorkerChannel` / `AsyncTaskrouterV1WorkerChannel` | 3 | `fetch_worker_channel` · `list_worker_channel` · `update_worker_channel` | -| `client.taskrouter_v1_worker_reservation` | `TaskrouterV1WorkerReservation` / `AsyncTaskrouterV1WorkerReservation` | 3 | `fetch_worker_reservation` · `list_worker_reservation` · `update_worker_reservation` | -| `client.taskrouter_v1_worker_statistics` | `TaskrouterV1WorkerStatistics` / `AsyncTaskrouterV1WorkerStatistics` | 1 | `fetch_worker_instance_statistics` | -| `client.taskrouter_v1_workers_cumulative_statistics` | `TaskrouterV1WorkersCumulativeStatistics` / `AsyncTaskrouterV1WorkersCumulativeStatistics` | 1 | `fetch_workers_cumulative_statistics` | -| `client.taskrouter_v1_workers_real_time_statistics` | `TaskrouterV1WorkersRealTimeStatistics` / `AsyncTaskrouterV1WorkersRealTimeStatistics` | 1 | `fetch_workers_real_time_statistics` | -| `client.taskrouter_v1_workers_statistics` | `TaskrouterV1WorkersStatistics` / `AsyncTaskrouterV1WorkersStatistics` | 1 | `fetch_worker_statistics` | -| `client.taskrouter_v1_workflow` | `TaskrouterV1Workflow` / `AsyncTaskrouterV1Workflow` | 5 | `create_workflow` · `delete_workflow` · `fetch_workflow` · `list_workflow` · `update_workflow` | -| `client.taskrouter_v1_workflow_cumulative_statistics` | `TaskrouterV1WorkflowCumulativeStatistics` / `AsyncTaskrouterV1WorkflowCumulativeStatistics` | 1 | `fetch_workflow_cumulative_statistics` | -| `client.taskrouter_v1_workflow_real_time_statistics` | `TaskrouterV1WorkflowRealTimeStatistics` / `AsyncTaskrouterV1WorkflowRealTimeStatistics` | 1 | `fetch_workflow_real_time_statistics` | -| `client.taskrouter_v1_workflow_statistics` | `TaskrouterV1WorkflowStatistics` / `AsyncTaskrouterV1WorkflowStatistics` | 1 | `fetch_workflow_statistics` | -| `client.taskrouter_v1_workspace_api` | `TaskrouterV1WorkspaceApi` / `AsyncTaskrouterV1WorkspaceApi` | 5 | `create_workspace` · `delete_workspace` · `fetch_workspace` · `list_workspace` · `update_workspace` | -| `client.taskrouter_v1_workspace_cumulative_statistics` | `TaskrouterV1WorkspaceCumulativeStatistics` / `AsyncTaskrouterV1WorkspaceCumulativeStatistics` | 1 | `fetch_workspace_cumulative_statistics` | -| `client.taskrouter_v1_workspace_real_time_statistics` | `TaskrouterV1WorkspaceRealTimeStatistics` / `AsyncTaskrouterV1WorkspaceRealTimeStatistics` | 1 | `fetch_workspace_real_time_statistics` | -| `client.taskrouter_v1_workspace_statistics` | `TaskrouterV1WorkspaceStatistics` / `AsyncTaskrouterV1WorkspaceStatistics` | 1 | `fetch_workspace_statistics` | -| `client.trusthub_v1_compliance_inquiries` | `TrusthubV1ComplianceInquiries` / `AsyncTrusthubV1ComplianceInquiries` | 2 | `create_compliance_inquiry` · `update_compliance_inquiry` | -| `client.trusthub_v1_compliance_registration_inquiries` | `TrusthubV1ComplianceRegistrationInquiries` / `AsyncTrusthubV1ComplianceRegistrationInquiries` | 2 | `create_compliance_registration` · `update_compliance_registration` | -| `client.trusthub_v1_compliance_tollfree_inquiries` | `TrusthubV1ComplianceTollfreeInquiries` / `AsyncTrusthubV1ComplianceTollfreeInquiries` | 1 | `create_compliance_tollfree_inquiry` | -| `client.trusthub_v1_customer_profiles` | `TrusthubV1CustomerProfiles` / `AsyncTrusthubV1CustomerProfiles` | 5 | `create_customer_profile` · `delete_customer_profile` · `fetch_customer_profile` · `list_customer_profile` · `update_customer_profile` | -| `client.trusthub_v1_customer_profiles_channel_endpoint_assignment` | `TrusthubV1CustomerProfilesChannelEndpointAssignment` / `AsyncTrusthubV1CustomerProfilesChannelEndpointAssignment` | 4 | `create_customer_profile_channel_endpoint_assignment` · `delete_customer_profile_channel_endpoint_assignment` · `fetch_customer_profile_channel_endpoint_assignment` · `list_customer_profile_channel_endpoint_assignment` | -| `client.trusthub_v1_customer_profiles_entity_assignments` | `TrusthubV1CustomerProfilesEntityAssignments` / `AsyncTrusthubV1CustomerProfilesEntityAssignments` | 4 | `create_customer_profile_entity_assignment` · `delete_customer_profile_entity_assignment` · `fetch_customer_profile_entity_assignment` · `list_customer_profile_entity_assignment` | -| `client.trusthub_v1_customer_profiles_evaluations` | `TrusthubV1CustomerProfilesEvaluations` / `AsyncTrusthubV1CustomerProfilesEvaluations` | 3 | `create_customer_profile_evaluation` · `fetch_customer_profile_evaluation` · `list_customer_profile_evaluation` | -| `client.trusthub_v1_end_user_api` | `TrusthubV1EndUserApi` / `AsyncTrusthubV1EndUserApi` | 5 | `create_end_user2` · `delete_end_user2` · `fetch_end_user2` · `list_end_user2` · `update_end_user2` | -| `client.trusthub_v1_end_user_type` | `TrusthubV1EndUserType` / `AsyncTrusthubV1EndUserType` | 2 | `fetch_end_user_type2` · `list_end_user_type2` | -| `client.trusthub_v1_policies_api` | `TrusthubV1PoliciesApi` / `AsyncTrusthubV1PoliciesApi` | 2 | `fetch_policies` · `list_policies` | -| `client.trusthub_v1_supporting_document_api` | `TrusthubV1SupportingDocumentApi` / `AsyncTrusthubV1SupportingDocumentApi` | 5 | `create_supporting_document2` · `delete_supporting_document2` · `fetch_supporting_document2` · `list_supporting_document2` · `update_supporting_document2` | -| `client.trusthub_v1_supporting_document_type` | `TrusthubV1SupportingDocumentType` / `AsyncTrusthubV1SupportingDocumentType` | 2 | `fetch_supporting_document_type2` · `list_supporting_document_type2` | -| `client.trusthub_v1_trust_products` | `TrusthubV1TrustProducts` / `AsyncTrusthubV1TrustProducts` | 5 | `create_trust_product` · `delete_trust_product` · `fetch_trust_product` · `list_trust_product` · `update_trust_product` | -| `client.trusthub_v1_trust_products_channel_endpoint_assignment` | `TrusthubV1TrustProductsChannelEndpointAssignment` / `AsyncTrusthubV1TrustProductsChannelEndpointAssignment` | 4 | `create_trust_product_channel_endpoint_assignment` · `delete_trust_product_channel_endpoint_assignment` · `fetch_trust_product_channel_endpoint_assignment` · `list_trust_product_channel_endpoint_assignment` | -| `client.trusthub_v1_trust_products_entity_assignments` | `TrusthubV1TrustProductsEntityAssignments` / `AsyncTrusthubV1TrustProductsEntityAssignments` | 4 | `create_trust_product_entity_assignment` · `delete_trust_product_entity_assignment` · `fetch_trust_product_entity_assignment` · `list_trust_product_entity_assignment` | -| `client.trusthub_v1_trust_products_evaluations` | `TrusthubV1TrustProductsEvaluations` / `AsyncTrusthubV1TrustProductsEvaluations` | 3 | `create_trust_product_evaluation` · `fetch_trust_product_evaluation` · `list_trust_product_evaluation` | -| `client.twilio_insights` | `TwilioInsights` / `AsyncTwilioInsights` | 3 | `create_query_results` · `fetch_metadata` · `fetch_query_results` | -| `client.v2_short_code_applications` | `V2ShortCodeApplications` / `AsyncV2ShortCodeApplications` | 3 | `create_short_code_application` · `fetch_short_code_application` · `list_short_code_applications` | -| `client.verify_v2_access_token` | `VerifyV2AccessToken` / `AsyncVerifyV2AccessToken` | 2 | `create_access_token` · `fetch_access_token` | -| `client.verify_v2_bucket` | `VerifyV2Bucket` / `AsyncVerifyV2Bucket` | 5 | `create_bucket` · `delete_bucket` · `fetch_bucket` · `list_bucket` · `update_bucket` | -| `client.verify_v2_challenge` | `VerifyV2Challenge` / `AsyncVerifyV2Challenge` | 4 | `create_challenge` · `fetch_challenge` · `list_challenge` · `update_challenge` | -| `client.verify_v2_entity` | `VerifyV2Entity` / `AsyncVerifyV2Entity` | 4 | `create_entity` · `delete_entity` · `fetch_entity` · `list_entity` | -| `client.verify_v2_factor` | `VerifyV2Factor` / `AsyncVerifyV2Factor` | 4 | `delete_factor` · `fetch_factor` · `list_factor` · `update_factor` | -| `client.verify_v2_form_api` | `VerifyV2FormApi` / `AsyncVerifyV2FormApi` | 1 | `fetch_form` | -| `client.verify_v2_messaging_configuration` | `VerifyV2MessagingConfiguration` / `AsyncVerifyV2MessagingConfiguration` | 5 | `create_messaging_configuration` · `delete_messaging_configuration` · `fetch_messaging_configuration` · `list_messaging_configuration` · `update_messaging_configuration` | -| `client.verify_v2_new_challenge` | `VerifyV2NewChallenge` / `AsyncVerifyV2NewChallenge` | 1 | `create_challenge_passkeys` | -| `client.verify_v2_new_factor` | `VerifyV2NewFactor` / `AsyncVerifyV2NewFactor` | 2 | `create_new_factor` · `create_new_factor_passkey` | -| `client.verify_v2_notification` | `VerifyV2Notification` / `AsyncVerifyV2Notification` | 1 | `create_notification` | -| `client.verify_v2_rate_limit` | `VerifyV2RateLimit` / `AsyncVerifyV2RateLimit` | 5 | `create_rate_limit` · `delete_rate_limit` · `fetch_rate_limit` · `list_rate_limit` · `update_rate_limit` | -| `client.verify_v2_safelist_api` | `VerifyV2SafelistApi` / `AsyncVerifyV2SafelistApi` | 3 | `create_safelist` · `delete_safelist` · `fetch_safelist` | -| `client.verify_v2_service_api` | `VerifyV2ServiceApi` / `AsyncVerifyV2ServiceApi` | 5 | `create_service2` · `delete_service2` · `fetch_service2` · `list_service2` · `update_service2` | -| `client.verify_v2_template` | `VerifyV2Template` / `AsyncVerifyV2Template` | 1 | `list_verification_template` | -| `client.verify_v2_verification` | `VerifyV2Verification` / `AsyncVerifyV2Verification` | 3 | `create_verification` · `fetch_verification` · `update_verification` | -| `client.verify_v2_verification_attempt_api` | `VerifyV2VerificationAttemptApi` / `AsyncVerifyV2VerificationAttemptApi` | 2 | `fetch_verification_attempt` · `list_verification_attempt` | -| `client.verify_v2_verification_attempts_summary_api` | `VerifyV2VerificationAttemptsSummaryApi` / `AsyncVerifyV2VerificationAttemptsSummaryApi` | 1 | `fetch_verification_attempts_summary` | -| `client.verify_v2_verification_check` | `VerifyV2VerificationCheck` / `AsyncVerifyV2VerificationCheck` | 1 | `create_verification_check` | -| `client.verify_v2_webhook` | `VerifyV2Webhook` / `AsyncVerifyV2Webhook` | 5 | `create_webhook` · `delete_webhook` · `fetch_webhook` · `list_webhook` · `update_webhook` | -| `client.video_v1_anonymize` | `VideoV1Anonymize` / `AsyncVideoV1Anonymize` | 1 | `update_room_participant_anonymize` | -| `client.video_v1_composition_api` | `VideoV1CompositionApi` / `AsyncVideoV1CompositionApi` | 4 | `create_composition` · `delete_composition` · `fetch_composition` · `list_composition` | -| `client.video_v1_composition_hook_api` | `VideoV1CompositionHookApi` / `AsyncVideoV1CompositionHookApi` | 5 | `create_composition_hook` · `delete_composition_hook` · `fetch_composition_hook` · `list_composition_hook` · `update_composition_hook` | -| `client.video_v1_composition_settings_api` | `VideoV1CompositionSettingsApi` / `AsyncVideoV1CompositionSettingsApi` | 2 | `create_composition_settings` · `fetch_composition_settings` | -| `client.video_v1_participant` | `VideoV1Participant` / `AsyncVideoV1Participant` | 3 | `fetch_room_participant` · `list_room_participant` · `update_room_participant` | -| `client.video_v1_published_track` | `VideoV1PublishedTrack` / `AsyncVideoV1PublishedTrack` | 2 | `fetch_room_participant_published_track` · `list_room_participant_published_track` | -| `client.video_v1_recording_api` | `VideoV1RecordingApi` / `AsyncVideoV1RecordingApi` | 3 | `delete_recording2` · `fetch_recording2` · `list_recording2` | -| `client.video_v1_recording_rules` | `VideoV1RecordingRules` / `AsyncVideoV1RecordingRules` | 2 | `fetch_room_recording_rule` · `update_room_recording_rule` | -| `client.video_v1_recording_settings_api` | `VideoV1RecordingSettingsApi` / `AsyncVideoV1RecordingSettingsApi` | 2 | `create_recording_settings` · `fetch_recording_settings` | -| `client.video_v1_room_api` | `VideoV1RoomApi` / `AsyncVideoV1RoomApi` | 4 | `create_room` · `fetch_room` · `list_room` · `update_room` | -| `client.video_v1_room_recording` | `VideoV1RoomRecording` / `AsyncVideoV1RoomRecording` | 3 | `delete_room_recording` · `fetch_room_recording` · `list_room_recording` | -| `client.video_v1_subscribe_rules` | `VideoV1SubscribeRules` / `AsyncVideoV1SubscribeRules` | 2 | `fetch_room_participant_subscribe_rule` · `update_room_participant_subscribe_rule` | -| `client.video_v1_subscribed_track` | `VideoV1SubscribedTrack` / `AsyncVideoV1SubscribedTrack` | 2 | `fetch_room_participant_subscribed_track` · `list_room_participant_subscribed_track` | -| `client.video_v1_transcriptions` | `VideoV1Transcriptions` / `AsyncVideoV1Transcriptions` | 4 | `create_room_transcriptions` · `fetch_room_transcriptions` · `list_room_transcriptions` · `update_room_transcriptions` | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `twilio_sdk/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `twilio_sdk/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/twilio-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `twilio_sdk/models/accounts_calls_recordings_sid_json201041408_error.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `twilio_sdk/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import twilio_sdk, pathlib; print(pathlib.Path(twilio_sdk.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/twilio_sdk` on Windows, `.venv/lib/python3.*/site-packages/twilio_sdk` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `account_sid_auth_token=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **128 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 128 operations that return `None`** — `delete_lookup_phone_number_overrides` · `delete_lookup_rate_limit` · `api20100401_add_on_result.delete_recording_add_on_result` · `api20100401_address.delete_address` · `api20100401_application.delete_application` · `api20100401_assigned_add_on.delete_incoming_phone_number_assigned_add_on` · `api20100401_auth_calls_credential_list_mapping.delete_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.delete_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.delete_sip_auth_registrations_credential_list_mapping` · `api20100401_call.delete_call` · `api20100401_call_recording.delete_call_recording` · `api20100401_conference_recording.delete_conference_recording` · `api20100401_connect_app.delete_connect_app` · `api20100401_credential.delete_sip_credential` · `api20100401_credential_list.delete_sip_credential_list` · `api20100401_credential_list_mapping.delete_sip_credential_list_mapping` · `api20100401_data.fetch_recording_add_on_result_payload_data` · `api20100401_domain.delete_sip_domain` · `api20100401_incoming_phone_number.delete_incoming_phone_number` · `api20100401_ip_access_control_list.delete_sip_ip_access_control_list` · `api20100401_ip_access_control_list_mapping.delete_sip_ip_access_control_list_mapping` · `api20100401_key.delete_key` · `api20100401_media_instance.delete_media` · `api20100401_message.delete_message` · `api20100401_outgoing_caller_id.delete_outgoing_caller_id` · `api20100401_participant.delete_participant` · `api20100401_payload.delete_recording_add_on_result_payload` · `api20100401_queue.delete_queue` · `api20100401_recording.delete_recording` · `api20100401_recording_transcription.delete_recording_transcription` · `api20100401_signing_key.delete_signing_key` · `api20100401_sip_ip_address.delete_sip_ip_address` · `api20100401_transcription.delete_transcription` · `api20100401_trigger.delete_usage_trigger` · `api20100401_user_defined_message_subscription.delete_user_defined_message_subscription` · `contentv1_content_api.delete_content` · `conversations_v1_address_configuration.delete_configuration_address` · `conversations_v1_binding.delete_service_binding` · `conversations_v1_conversation_api.delete_conversation` · `conversations_v1_conversation_api.delete_service_conversation` · `conversations_v1_credential_api.delete_credential` · `conversations_v1_message.delete_conversation_message` · `conversations_v1_message.delete_service_conversation_message` · `conversations_v1_participant.delete_conversation_participant` · `conversations_v1_participant.delete_service_conversation_participant` · `conversations_v1_role_api.delete_role` · `conversations_v1_role_api.delete_service_role` · `conversations_v1_service_api.delete_service3` · `conversations_v1_user_api.delete_service_user` · `conversations_v1_user_api.delete_user` · `conversations_v1_user_conversation.delete_service_user_conversation` · `conversations_v1_user_conversation.delete_user_conversation` · `conversations_v1_webhook.delete_conversation_scoped_webhook` · `conversations_v1_webhook.delete_service_conversation_scoped_webhook` · `flex_v1_channel_api.delete_channel` · `flex_v1_flex_flow_api.delete_flex_flow` · `flex_v1_insights_questionnaires_api.delete_insights_questionnaires` · `flex_v1_insights_questionnaires_category_api.delete_insights_questionnaires_category` · `flex_v1_insights_questionnaires_question_api.delete_insights_questionnaires_question` · `flex_v1_web_channel_api.delete_web_channel` · `messaging_v1_alpha_sender.delete_alpha_sender` · `messaging_v1_channel_sender.delete_channel_sender` · `messaging_v1_deactivations.fetch_deactivation` · `messaging_v1_destination_alpha_sender.delete_destination_alpha_sender` · `messaging_v1_domain_certs.delete_domain_cert_v4` · `messaging_v1_linkshortening_messaging_service_api.delete_linkshortening_messaging_service` · `messaging_v1_phone_number.delete_phone_number` · `messaging_v1_service_api.delete_service` · `messaging_v1_short_code.delete_short_code` · `messaging_v1_tollfree_verification_api.delete_tollfree_verification` · `messaging_v1_us_app_to_person.delete_us_app_to_person` · `messaging_v2_channels_sender.delete_channels_sender` · `numbers_v1_porting_port_in_api.delete_porting_port_in` · `numbers_v1_porting_port_in_phone_number_api.delete_porting_port_in_phone_number` · `numbers_v1_porting_webhook_configuration_delete_api.delete_porting_webhook_configuration_delete` · `numbers_v2_authorization_document_api.delete_authorization_document` · `numbers_v2_bundle.delete_bundle` · `numbers_v2_end_user.delete_end_user` · `numbers_v2_hosted_number_order_api.delete_hosted_number_order` · `numbers_v2_item_assignment.delete_item_assignment` · `numbers_v2_supporting_document.delete_supporting_document` · `proxy_v1_interaction.delete_interaction` · `proxy_v1_participant.delete_participant2` · `proxy_v1_phone_number.delete_phone_number2` · `proxy_v1_service_api.delete_service4` · `proxy_v1_session.delete_session` · `studio_v1_engagement.delete_engagement` · `studio_v1_execution.delete_execution` · `studio_v1_flow_api.delete_flow` · `studio_v2_execution.delete_execution2` · `studio_v2_flow_api.delete_flow2` · `sync_v1_document.delete_document` · `sync_v1_document_permission.delete_document_permission` · `sync_v1_service_api.delete_service5` · `sync_v1_sync_list.delete_sync_list` · `sync_v1_sync_list_item.delete_sync_list_item` · `sync_v1_sync_list_permission.delete_sync_list_permission` · `sync_v1_sync_map.delete_sync_map` · `sync_v1_sync_map_item.delete_sync_map_item` · `sync_v1_sync_map_permission.delete_sync_map_permission` · `sync_v1_sync_stream.delete_sync_stream` · `taskrouter_v1_activity.delete_activity` · `taskrouter_v1_task.delete_task` · `taskrouter_v1_task_channel.delete_task_channel` · `taskrouter_v1_task_queue.delete_task_queue` · `taskrouter_v1_worker.delete_worker` · `taskrouter_v1_workflow.delete_workflow` · `taskrouter_v1_workspace_api.delete_workspace` · `trusthub_v1_customer_profiles.delete_customer_profile` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.delete_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_entity_assignments.delete_customer_profile_entity_assignment` · `trusthub_v1_end_user_api.delete_end_user2` · `trusthub_v1_supporting_document_api.delete_supporting_document2` · `trusthub_v1_trust_products.delete_trust_product` · `trusthub_v1_trust_products_channel_endpoint_assignment.delete_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_entity_assignments.delete_trust_product_entity_assignment` · `verify_v2_bucket.delete_bucket` · `verify_v2_entity.delete_entity` · `verify_v2_factor.delete_factor` · `verify_v2_messaging_configuration.delete_messaging_configuration` · `verify_v2_rate_limit.delete_rate_limit` · `verify_v2_safelist_api.delete_safelist` · `verify_v2_service_api.delete_service2` · `verify_v2_webhook.delete_webhook` · `video_v1_composition_api.delete_composition` · `video_v1_composition_hook_api.delete_composition_hook` · `video_v1_recording_api.delete_recording2` · `video_v1_room_recording.delete_room_recording`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — there are **seven** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `AccountsCallsRecordingsSidJson201041408Error1` — 34 operations (`create_lookup_phone_number_overrides` · `delete_lookup_phone_number_overrides` · `delete_lookup_rate_limit` · `fetch_lookup_account_rate_limits` · `fetch_lookup_phone_number_overrides` · `fetch_lookup_rate_limit` · `update_lookup_phone_number_overrides` · `update_lookup_rate_limit` · `api20100401_call_recording.update_call_recording` · `conversations_v2_action_api.create_conversation_action` · `conversations_v2_action_api.fetch_conversation_action` · `conversations_v2_communication_api.create_communication_in_conversation` · `conversations_v2_communication_api.fetch_communication` · `conversations_v2_communication_api.list_communication_by_conversation` · `conversations_v2_configuration_api.create_configuration` · `conversations_v2_configuration_api.delete_configuration` · `conversations_v2_configuration_api.fetch_configuration2` · `conversations_v2_configuration_api.list_configuration` · `conversations_v2_configuration_api.update_configuration2` · `conversations_v2_conversation_api.create_conversation_with_config` · `conversations_v2_conversation_api.delete_conversation_async` · `conversations_v2_conversation_api.fetch_conversation2` · `conversations_v2_conversation_api.list_conversation_by_account` · `conversations_v2_conversation_api.patch_conversation_by_id` · `conversations_v2_conversation_api.update_conversation_by_id` · `conversations_v2_operation.fetch_operation_status` · `conversations_v2_participant_api.create_participant_in_conversation` · `conversations_v2_participant_api.fetch_participant2` · `conversations_v2_participant_api.list_participant_by_conversation` · `conversations_v2_participant_api.update_participant_in_conversation` · `messaging_v3_typing_indicator.create_v3_typing_indicator` · `numbers_v1_sender_id_registration.create_sender_id_registration` · `numbers_v1_sender_id_registration_embedded_session.create_sender_id_registration_embedded_session` · `verify_v2_verification.create_verification`); distinguishing members *none required* - 2. `V3InsightsDomainsConversationsQuery400Error1` — 2 operations (`twilio_insights.create_query_results` · `twilio_insights.fetch_query_results`); distinguishing members `code` · `message` · `http_status_code` - 3. `V3InsightsDomainsConversationsQuery429Error1` — 2 operations (`twilio_insights.create_query_results` · `twilio_insights.fetch_query_results`); distinguishing members `code` · `message` · `http_status_code` - 4. `V3InsightsDomainsConversationsQuery500Error1` — 2 operations (`twilio_insights.create_query_results` · `twilio_insights.fetch_query_results`); distinguishing members `code` · `message` · `http_status_code` - 5. `V3InsightsDomainsConversationsMetadata400Error1` — 1 operation (`twilio_insights.fetch_metadata`); distinguishing members `code` · `message` · `http_status_code` - 6. `V3InsightsDomainsConversationsMetadata429Error1` — 1 operation (`twilio_insights.fetch_metadata`); distinguishing members `code` · `message` · `http_status_code` - 7. `V3InsightsDomainsConversationsMetadata500Error1` — 1 operation (`twilio_insights.fetch_metadata`); distinguishing members `code` · `message` · `http_status_code` - 8. *(none)* — `create_bulk_lookup` · `update_challenge_passkeys` · `update_passkeys_factor` · `api20100401_account.create_account` · `api20100401_account.fetch_account` · `api20100401_account.list_account` · `api20100401_account.update_account` · `api20100401_add_on_result.delete_recording_add_on_result` · `api20100401_add_on_result.fetch_recording_add_on_result` · `api20100401_add_on_result.list_recording_add_on_result` · `api20100401_address.create_address` · `api20100401_address.delete_address` · `api20100401_address.fetch_address` · `api20100401_address.list_address` · `api20100401_address.update_address` · `api20100401_all_time.list_usage_record_all_time` · `api20100401_application.create_application` · `api20100401_application.delete_application` · `api20100401_application.fetch_application` · `api20100401_application.list_application` · `api20100401_application.update_application` · `api20100401_assigned_add_on.create_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on.delete_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on.fetch_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on.list_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on_extension.fetch_incoming_phone_number_assigned_add_on_extension` · `api20100401_assigned_add_on_extension.list_incoming_phone_number_assigned_add_on_extension` · `api20100401_auth_calls_credential_list_mapping.create_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_credential_list_mapping.delete_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_credential_list_mapping.fetch_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_credential_list_mapping.list_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.create_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.delete_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.fetch_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.list_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.create_sip_auth_registrations_credential_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.delete_sip_auth_registrations_credential_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.fetch_sip_auth_registrations_credential_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.list_sip_auth_registrations_credential_list_mapping` · `api20100401_authorized_connect_app.fetch_authorized_connect_app` · `api20100401_authorized_connect_app.list_authorized_connect_app` · `api20100401_available_phone_number_country.fetch_available_phone_number_country` · `api20100401_available_phone_number_country.list_available_phone_number_country` · `api20100401_balance.fetch_balance` · `api20100401_call.create_call` · `api20100401_call.delete_call` · `api20100401_call.fetch_call` · `api20100401_call.list_call` · `api20100401_call.update_call` · `api20100401_call_notification.fetch_call_notification` · `api20100401_call_notification.list_call_notification` · `api20100401_call_recording.create_call_recording` · `api20100401_call_recording.delete_call_recording` · `api20100401_call_recording.fetch_call_recording` · `api20100401_call_recording.list_call_recording` · `api20100401_call_transcription.create_realtime_transcription` · `api20100401_call_transcription.update_realtime_transcription` · `api20100401_conference.fetch_conference` · `api20100401_conference.list_conference` · `api20100401_conference.update_conference` · `api20100401_conference_recording.delete_conference_recording` · `api20100401_conference_recording.fetch_conference_recording` · `api20100401_conference_recording.list_conference_recording` · `api20100401_conference_recording.update_conference_recording` · `api20100401_connect_app.delete_connect_app` · `api20100401_connect_app.fetch_connect_app` · `api20100401_connect_app.list_connect_app` · `api20100401_connect_app.update_connect_app` · `api20100401_credential.create_sip_credential` · `api20100401_credential.delete_sip_credential` · `api20100401_credential.fetch_sip_credential` · `api20100401_credential.list_sip_credential` · `api20100401_credential.update_sip_credential` · `api20100401_credential_list.create_sip_credential_list` · `api20100401_credential_list.delete_sip_credential_list` · `api20100401_credential_list.fetch_sip_credential_list` · `api20100401_credential_list.list_sip_credential_list` · `api20100401_credential_list.update_sip_credential_list` · `api20100401_credential_list_mapping.create_sip_credential_list_mapping` · `api20100401_credential_list_mapping.delete_sip_credential_list_mapping` · `api20100401_credential_list_mapping.fetch_sip_credential_list_mapping` · `api20100401_credential_list_mapping.list_sip_credential_list_mapping` · `api20100401_daily.list_usage_record_daily` · `api20100401_data.fetch_recording_add_on_result_payload_data` · `api20100401_dependent_phone_number.list_dependent_phone_number` · `api20100401_domain.create_sip_domain` · `api20100401_domain.delete_sip_domain` · `api20100401_domain.fetch_sip_domain` · `api20100401_domain.list_sip_domain` · `api20100401_domain.update_sip_domain` · `api20100401_event.list_call_event` · `api20100401_feedback.create_message_feedback` · `api20100401_incoming_phone_number.create_incoming_phone_number` · `api20100401_incoming_phone_number.delete_incoming_phone_number` · `api20100401_incoming_phone_number.fetch_incoming_phone_number` · `api20100401_incoming_phone_number.list_incoming_phone_number` · `api20100401_incoming_phone_number.update_incoming_phone_number` · `api20100401_incoming_phone_number_local.create_incoming_phone_number_local` · `api20100401_incoming_phone_number_local.list_incoming_phone_number_local` · `api20100401_incoming_phone_number_mobile.create_incoming_phone_number_mobile` · `api20100401_incoming_phone_number_mobile.list_incoming_phone_number_mobile` · `api20100401_incoming_phone_number_toll_free.create_incoming_phone_number_toll_free` · `api20100401_incoming_phone_number_toll_free.list_incoming_phone_number_toll_free` · `api20100401_ip_access_control_list.create_sip_ip_access_control_list` · `api20100401_ip_access_control_list.delete_sip_ip_access_control_list` · `api20100401_ip_access_control_list.fetch_sip_ip_access_control_list` · `api20100401_ip_access_control_list.list_sip_ip_access_control_list` · `api20100401_ip_access_control_list.update_sip_ip_access_control_list` · `api20100401_ip_access_control_list_mapping.create_sip_ip_access_control_list_mapping` · `api20100401_ip_access_control_list_mapping.delete_sip_ip_access_control_list_mapping` · `api20100401_ip_access_control_list_mapping.fetch_sip_ip_access_control_list_mapping` · `api20100401_ip_access_control_list_mapping.list_sip_ip_access_control_list_mapping` · `api20100401_key.delete_key` · `api20100401_key.fetch_key` · `api20100401_key.list_key` · `api20100401_key.update_key` · `api20100401_last_month.list_usage_record_last_month` · `api20100401_local.list_available_phone_number_local` · `api20100401_machine_to_machine.list_available_phone_number_machine_to_machine` · `api20100401_media.list_media` · `api20100401_media_instance.delete_media` · `api20100401_media_instance.fetch_media` · `api20100401_member.fetch_member` · `api20100401_member.list_member` · `api20100401_member.update_member` · `api20100401_message.create_message` · `api20100401_message.delete_message` · `api20100401_message.fetch_message` · `api20100401_message.list_message` · `api20100401_message.update_message` · `api20100401_mobile.list_available_phone_number_mobile` · `api20100401_monthly.list_usage_record_monthly` · `api20100401_national.list_available_phone_number_national` · `api20100401_new_key.create_new_key` · `api20100401_new_signing_key.create_new_signing_key` · `api20100401_notification.fetch_notification` · `api20100401_notification.list_notification` · `api20100401_outgoing_caller_id.delete_outgoing_caller_id` · `api20100401_outgoing_caller_id.fetch_outgoing_caller_id` · `api20100401_outgoing_caller_id.list_outgoing_caller_id` · `api20100401_outgoing_caller_id.update_outgoing_caller_id` · `api20100401_participant.create_participant` · `api20100401_participant.delete_participant` · `api20100401_participant.fetch_participant` · `api20100401_participant.list_participant` · `api20100401_participant.update_participant` · `api20100401_payload.delete_recording_add_on_result_payload` · `api20100401_payload.fetch_recording_add_on_result_payload` · `api20100401_payload.list_recording_add_on_result_payload` · `api20100401_payment.create_payments` · `api20100401_payment.update_payments` · `api20100401_queue.create_queue` · `api20100401_queue.delete_queue` · `api20100401_queue.fetch_queue` · `api20100401_queue.list_queue` · `api20100401_queue.update_queue` · `api20100401_record.list_usage_record` · `api20100401_recording.delete_recording` · `api20100401_recording.fetch_recording` · `api20100401_recording.list_recording` · `api20100401_recording_transcription.delete_recording_transcription` · `api20100401_recording_transcription.fetch_recording_transcription` · `api20100401_recording_transcription.list_recording_transcription` · `api20100401_shared_cost.list_available_phone_number_shared_cost` · `api20100401_short_code.fetch_short_code` · `api20100401_short_code.list_short_code` · `api20100401_short_code.update_short_code` · `api20100401_signing_key.delete_signing_key` · `api20100401_signing_key.fetch_signing_key` · `api20100401_signing_key.list_signing_key` · `api20100401_signing_key.update_signing_key` · `api20100401_sip_ip_address.create_sip_ip_address` · `api20100401_sip_ip_address.delete_sip_ip_address` · `api20100401_sip_ip_address.fetch_sip_ip_address` · `api20100401_sip_ip_address.list_sip_ip_address` · `api20100401_sip_ip_address.update_sip_ip_address` · `api20100401_siprec.create_siprec` · `api20100401_siprec.update_siprec` · `api20100401_stream.create_stream` · `api20100401_stream.update_stream` · `api20100401_this_month.list_usage_record_this_month` · `api20100401_today.list_usage_record_today` · `api20100401_token.create_token` · `api20100401_toll_free.list_available_phone_number_toll_free` · `api20100401_transcription.delete_transcription` · `api20100401_transcription.fetch_transcription` · `api20100401_transcription.list_transcription` · `api20100401_trigger.create_usage_trigger` · `api20100401_trigger.delete_usage_trigger` · `api20100401_trigger.fetch_usage_trigger` · `api20100401_trigger.list_usage_trigger` · `api20100401_trigger.update_usage_trigger` · `api20100401_user_defined_message.create_user_defined_message` · `api20100401_user_defined_message_subscription.create_user_defined_message_subscription` · `api20100401_user_defined_message_subscription.delete_user_defined_message_subscription` · `api20100401_validation_request.create_validation_request` · `api20100401_voip.list_available_phone_number_voip` · `api20100401_yearly.list_usage_record_yearly` · `api20100401_yesterday.list_usage_record_yesterday` · `content_v2_content.list_content2` · `content_v2_content_and_approvals.list_content_and_approvals2` · `contentv1_approval_create.create_approval_create` · `contentv1_approval_fetch.fetch_approval_fetch` · `contentv1_content_api.create_content` · `contentv1_content_api.delete_content` · `contentv1_content_api.fetch_content` · `contentv1_content_api.list_content` · `contentv1_content_api.update_content` · `contentv1_content_and_approvals_api.list_content_and_approvals` · `contentv1_legacy_content_api.list_legacy_content` · `conversations_v1_address_configuration.create_configuration_address` · `conversations_v1_address_configuration.delete_configuration_address` · `conversations_v1_address_configuration.fetch_configuration_address` · `conversations_v1_address_configuration.list_configuration_address` · `conversations_v1_address_configuration.update_configuration_address` · `conversations_v1_binding.delete_service_binding` · `conversations_v1_binding.fetch_service_binding` · `conversations_v1_binding.list_service_binding` · `conversations_v1_configuration_api.fetch_configuration` · `conversations_v1_configuration_api.fetch_service_configuration` · `conversations_v1_configuration_api.update_configuration` · `conversations_v1_configuration_api.update_service_configuration` · `conversations_v1_conversation_api.create_conversation` · `conversations_v1_conversation_api.create_service_conversation` · `conversations_v1_conversation_api.delete_conversation` · `conversations_v1_conversation_api.delete_service_conversation` · `conversations_v1_conversation_api.fetch_conversation` · `conversations_v1_conversation_api.fetch_service_conversation` · `conversations_v1_conversation_api.list_conversation` · `conversations_v1_conversation_api.list_service_conversation` · `conversations_v1_conversation_api.update_conversation` · `conversations_v1_conversation_api.update_service_conversation` · `conversations_v1_conversation_with_participants_api.create_conversation_with_participants` · `conversations_v1_conversation_with_participants_api.create_service_conversation_with_participants` · `conversations_v1_credential_api.create_credential` · `conversations_v1_credential_api.delete_credential` · `conversations_v1_credential_api.fetch_credential` · `conversations_v1_credential_api.list_credential` · `conversations_v1_credential_api.update_credential` · `conversations_v1_delivery_receipt.fetch_conversation_message_receipt` · `conversations_v1_delivery_receipt.fetch_service_conversation_message_receipt` · `conversations_v1_delivery_receipt.list_conversation_message_receipt` · `conversations_v1_delivery_receipt.list_service_conversation_message_receipt` · `conversations_v1_message.create_conversation_message` · `conversations_v1_message.create_service_conversation_message` · `conversations_v1_message.delete_conversation_message` · `conversations_v1_message.delete_service_conversation_message` · `conversations_v1_message.fetch_conversation_message` · `conversations_v1_message.fetch_service_conversation_message` · `conversations_v1_message.list_conversation_message` · `conversations_v1_message.list_service_conversation_message` · `conversations_v1_message.update_conversation_message` · `conversations_v1_message.update_service_conversation_message` · `conversations_v1_notification.fetch_service_notification` · `conversations_v1_notification.update_service_notification` · `conversations_v1_participant.create_conversation_participant` · `conversations_v1_participant.create_service_conversation_participant` · `conversations_v1_participant.delete_conversation_participant` · `conversations_v1_participant.delete_service_conversation_participant` · `conversations_v1_participant.fetch_conversation_participant` · `conversations_v1_participant.fetch_service_conversation_participant` · `conversations_v1_participant.list_conversation_participant` · `conversations_v1_participant.list_service_conversation_participant` · `conversations_v1_participant.update_conversation_participant` · `conversations_v1_participant.update_service_conversation_participant` · `conversations_v1_participant_conversation_api.list_participant_conversation` · `conversations_v1_participant_conversation_api.list_service_participant_conversation` · `conversations_v1_role_api.create_role` · `conversations_v1_role_api.create_service_role` · `conversations_v1_role_api.delete_role` · `conversations_v1_role_api.delete_service_role` · `conversations_v1_role_api.fetch_role` · `conversations_v1_role_api.fetch_service_role` · `conversations_v1_role_api.list_role` · `conversations_v1_role_api.list_service_role` · `conversations_v1_role_api.update_role` · `conversations_v1_role_api.update_service_role` · `conversations_v1_service_api.create_service3` · `conversations_v1_service_api.delete_service3` · `conversations_v1_service_api.fetch_service3` · `conversations_v1_service_api.list_service3` · `conversations_v1_user_api.create_service_user` · `conversations_v1_user_api.create_user` · `conversations_v1_user_api.delete_service_user` · `conversations_v1_user_api.delete_user` · `conversations_v1_user_api.fetch_service_user` · `conversations_v1_user_api.fetch_user` · `conversations_v1_user_api.list_service_user` · `conversations_v1_user_api.list_user` · `conversations_v1_user_api.update_service_user` · `conversations_v1_user_api.update_user` · `conversations_v1_user_conversation.delete_service_user_conversation` · `conversations_v1_user_conversation.delete_user_conversation` · `conversations_v1_user_conversation.fetch_service_user_conversation` · `conversations_v1_user_conversation.fetch_user_conversation` · `conversations_v1_user_conversation.list_service_user_conversation` · `conversations_v1_user_conversation.list_user_conversation` · `conversations_v1_user_conversation.update_service_user_conversation` · `conversations_v1_user_conversation.update_user_conversation` · `conversations_v1_webhook.create_conversation_scoped_webhook` · `conversations_v1_webhook.create_service_conversation_scoped_webhook` · `conversations_v1_webhook.delete_conversation_scoped_webhook` · `conversations_v1_webhook.delete_service_conversation_scoped_webhook` · `conversations_v1_webhook.fetch_configuration_webhook` · `conversations_v1_webhook.fetch_conversation_scoped_webhook` · `conversations_v1_webhook.fetch_service_conversation_scoped_webhook` · `conversations_v1_webhook.fetch_service_webhook_configuration` · `conversations_v1_webhook.list_conversation_scoped_webhook` · `conversations_v1_webhook.list_service_conversation_scoped_webhook` · `conversations_v1_webhook.update_configuration_webhook` · `conversations_v1_webhook.update_conversation_scoped_webhook` · `conversations_v1_webhook.update_service_conversation_scoped_webhook` · `conversations_v1_webhook.update_service_webhook_configuration` · `flex_v1_assessments.create_insights_assessments` · `flex_v1_assessments.list_insights_assessments` · `flex_v1_assessments.update_insights_assessments` · `flex_v1_channel_api.create_channel` · `flex_v1_channel_api.delete_channel` · `flex_v1_channel_api.fetch_channel` · `flex_v1_channel_api.list_channel` · `flex_v1_configuration_api.fetch_configuration3` · `flex_v1_configuration_api.update_configuration3` · `flex_v1_configured_plugin.fetch_configured_plugin` · `flex_v1_configured_plugin.list_configured_plugin` · `flex_v1_flex_flow_api.create_flex_flow` · `flex_v1_flex_flow_api.delete_flex_flow` · `flex_v1_flex_flow_api.fetch_flex_flow` · `flex_v1_flex_flow_api.list_flex_flow` · `flex_v1_flex_flow_api.update_flex_flow` · `flex_v1_insights_assessments_comment_api.create_insights_assessments_comment` · `flex_v1_insights_assessments_comment_api.list_insights_assessments_comment` · `flex_v1_insights_conversations_api.list_insights_conversations` · `flex_v1_insights_questionnaires_api.create_insights_questionnaires` · `flex_v1_insights_questionnaires_api.delete_insights_questionnaires` · `flex_v1_insights_questionnaires_api.fetch_insights_questionnaires` · `flex_v1_insights_questionnaires_api.list_insights_questionnaires` · `flex_v1_insights_questionnaires_api.update_insights_questionnaires` · `flex_v1_insights_questionnaires_category_api.create_insights_questionnaires_category` · `flex_v1_insights_questionnaires_category_api.delete_insights_questionnaires_category` · `flex_v1_insights_questionnaires_category_api.list_insights_questionnaires_category` · `flex_v1_insights_questionnaires_category_api.update_insights_questionnaires_category` · `flex_v1_insights_questionnaires_question_api.create_insights_questionnaires_question` · `flex_v1_insights_questionnaires_question_api.delete_insights_questionnaires_question` · `flex_v1_insights_questionnaires_question_api.list_insights_questionnaires_question` · `flex_v1_insights_questionnaires_question_api.update_insights_questionnaires_question` · `flex_v1_insights_segments_api.list_insights_segments` · `flex_v1_insights_session_api.create_insights_session` · `flex_v1_insights_settings_answer_sets_api.fetch_insights_settings_answersets` · `flex_v1_insights_settings_comment_api.fetch_insights_settings_comment` · `flex_v1_insights_user_roles_api.fetch_insights_user_roles` · `flex_v1_interaction_api.create_interaction` · `flex_v1_interaction_api.fetch_interaction2` · `flex_v1_interaction_api.update_interaction` · `flex_v1_interaction_channel.fetch_interaction_channel` · `flex_v1_interaction_channel.list_interaction_channel` · `flex_v1_interaction_channel.update_interaction_channel` · `flex_v1_interaction_channel_invite.create_interaction_channel_invite` · `flex_v1_interaction_channel_invite.list_interaction_channel_invite` · `flex_v1_interaction_channel_participant.create_interaction_channel_participant` · `flex_v1_interaction_channel_participant.list_interaction_channel_participant` · `flex_v1_interaction_channel_participant.update_interaction_channel_participant` · `flex_v1_interaction_transfer.create_interaction_transfer` · `flex_v1_interaction_transfer.fetch_interaction_transfer` · `flex_v1_interaction_transfer.update_interaction_transfer` · `flex_v1_plugin_api.create_plugin` · `flex_v1_plugin_api.fetch_plugin` · `flex_v1_plugin_api.list_plugin` · `flex_v1_plugin_api.update_plugin` · `flex_v1_plugin_archive_api.update_plugin_archive` · `flex_v1_plugin_configuration_api.create_plugin_configuration` · `flex_v1_plugin_configuration_api.fetch_plugin_configuration` · `flex_v1_plugin_configuration_api.list_plugin_configuration` · `flex_v1_plugin_configuration_archive_api.update_plugin_configuration_archive` · `flex_v1_plugin_release_api.create_plugin_release` · `flex_v1_plugin_release_api.fetch_plugin_release` · `flex_v1_plugin_release_api.list_plugin_release` · `flex_v1_plugin_version_archive_api.update_plugin_version_archive` · `flex_v1_plugin_versions.create_plugin_version` · `flex_v1_plugin_versions.fetch_plugin_version` · `flex_v1_plugin_versions.list_plugin_version` · `flex_v1_provisioning_status_api.fetch_provisioning_status` · `flex_v1_web_channel_api.create_web_channel` · `flex_v1_web_channel_api.delete_web_channel` · `flex_v1_web_channel_api.fetch_web_channel` · `flex_v1_web_channel_api.list_web_channel` · `flex_v1_web_channel_api.update_web_channel` · `flex_v2_flex_user_api.fetch_flex_user` · `flex_v2_flex_user_api.update_flex_user` · `flex_v2_web_channels.create_web_channel2` · `insights_v1_annotation.fetch_annotation` · `insights_v1_annotation.update_annotation` · `insights_v1_call_api.fetch_call2` · `insights_v1_call_summaries_api.list_call_summaries` · `insights_v1_call_summary_api.fetch_summary` · `insights_v1_conference_api.fetch_conference2` · `insights_v1_conference_api.list_conference2` · `insights_v1_conference_participant.fetch_conference_participant` · `insights_v1_conference_participant.list_conference_participant` · `insights_v1_create_account_report.create_account_report` · `insights_v1_create_inbound_phone_numbers_report.create_inbound_phone_numbers_report` · `insights_v1_create_outbound_phone_numbers_report.create_outbound_phone_numbers_report` · `insights_v1_event.list_event2` · `insights_v1_get_account_report.fetch_account_report` · `insights_v1_get_inbound_phone_numbers_report.list_inbound_phone_numbers_report` · `insights_v1_get_outbound_phone_numbers_report.list_outbound_phone_numbers_report` · `insights_v1_metric.list_metric` · `insights_v1_participant.fetch_video_participant_summary` · `insights_v1_participant.list_video_participant_summary` · `insights_v1_room.fetch_video_room_summary` · `insights_v1_room.list_video_room_summary` · `insights_v1_setting.fetch_account_settings` · `insights_v1_setting.update_account_settings` · `lookups_v1_phone_number_api.fetch_phone_number2` · `lookups_v2_phone_number.fetch_phone_number3` · `messaging_v1_alpha_sender.create_alpha_sender` · `messaging_v1_alpha_sender.delete_alpha_sender` · `messaging_v1_alpha_sender.fetch_alpha_sender` · `messaging_v1_alpha_sender.list_alpha_sender` · `messaging_v1_brand_registration.create_brand_registrations` · `messaging_v1_brand_registration.fetch_brand_registrations` · `messaging_v1_brand_registration.list_brand_registrations` · `messaging_v1_brand_registration.update_brand_registrations` · `messaging_v1_brand_registration_otp.create_brand_registration_otp` · `messaging_v1_brand_vetting.create_brand_vetting` · `messaging_v1_brand_vetting.fetch_brand_vetting` · `messaging_v1_brand_vetting.list_brand_vetting` · `messaging_v1_channel_sender.create_channel_sender` · `messaging_v1_channel_sender.delete_channel_sender` · `messaging_v1_channel_sender.fetch_channel_sender` · `messaging_v1_channel_sender.list_channel_sender` · `messaging_v1_deactivations.fetch_deactivation` · `messaging_v1_destination_alpha_sender.create_destination_alpha_sender` · `messaging_v1_destination_alpha_sender.delete_destination_alpha_sender` · `messaging_v1_destination_alpha_sender.fetch_destination_alpha_sender` · `messaging_v1_destination_alpha_sender.list_destination_alpha_sender` · `messaging_v1_domain_certs.delete_domain_cert_v4` · `messaging_v1_domain_certs.fetch_domain_cert_v4` · `messaging_v1_domain_certs.update_domain_cert_v4` · `messaging_v1_domain_config_api.fetch_domain_config` · `messaging_v1_domain_config_api.update_domain_config` · `messaging_v1_domain_config_messaging_service_api.fetch_domain_config_messaging_service` · `messaging_v1_domain_validate_dns.fetch_domain_dns_validation` · `messaging_v1_external_campaign_api.create_external_campaign` · `messaging_v1_linkshortening_messaging_service_api.create_linkshortening_messaging_service` · `messaging_v1_linkshortening_messaging_service_api.delete_linkshortening_messaging_service` · `messaging_v1_linkshortening_messaging_service_domain_association_api.fetch_linkshortening_messaging_service_domain_association` · `messaging_v1_phone_number.create_phone_number` · `messaging_v1_phone_number.delete_phone_number` · `messaging_v1_phone_number.fetch_phone_number` · `messaging_v1_phone_number.list_phone_number` · `messaging_v1_request_managed_cert_api.update_request_managed_cert` · `messaging_v1_service_api.create_service` · `messaging_v1_service_api.delete_service` · `messaging_v1_service_api.fetch_service` · `messaging_v1_service_api.list_service` · `messaging_v1_service_api.update_service` · `messaging_v1_short_code.create_short_code` · `messaging_v1_short_code.delete_short_code` · `messaging_v1_short_code.fetch_short_code2` · `messaging_v1_short_code.list_short_code2` · `messaging_v1_tollfree_verification_api.create_tollfree_verification` · `messaging_v1_tollfree_verification_api.delete_tollfree_verification` · `messaging_v1_tollfree_verification_api.fetch_tollfree_verification` · `messaging_v1_tollfree_verification_api.list_tollfree_verification` · `messaging_v1_tollfree_verification_api.update_tollfree_verification` · `messaging_v1_us_app_to_person.create_us_app_to_person` · `messaging_v1_us_app_to_person.delete_us_app_to_person` · `messaging_v1_us_app_to_person.fetch_us_app_to_person` · `messaging_v1_us_app_to_person.list_us_app_to_person` · `messaging_v1_us_app_to_person.update_us_app_to_person` · `messaging_v1_us_app_to_person_usecase.fetch_us_app_to_person_usecase` · `messaging_v1_usecase_api.fetch_usecase` · `messaging_v2_channels_sender.create_channels_sender` · `messaging_v2_channels_sender.delete_channels_sender` · `messaging_v2_channels_sender.fetch_channels_sender` · `messaging_v2_channels_sender.list_channels_sender` · `messaging_v2_channels_sender.update_channels_sender` · `messaging_v2_domain_certs.fetch_domain_cert_v42` · `messaging_v2_typing_indicator.create_typing_indicator` · `numbers_v1_bulk_eligibility_api.create_bulk_eligibility` · `numbers_v1_bulk_eligibility_api.fetch_bulk_eligibility` · `numbers_v1_eligibility_api.create_eligibility` · `numbers_v1_porting_port_in_api.create_porting_port_in` · `numbers_v1_porting_port_in_api.delete_porting_port_in` · `numbers_v1_porting_port_in_api.fetch_porting_port_in` · `numbers_v1_porting_port_in_api.list_port_in_requests` · `numbers_v1_porting_port_in_phone_number_api.delete_porting_port_in_phone_number` · `numbers_v1_porting_port_in_phone_number_api.fetch_porting_port_in_phone_number` · `numbers_v1_porting_portability_api.fetch_porting_portability` · `numbers_v1_porting_webhook_configuration_api.create_porting_webhook_configuration` · `numbers_v1_porting_webhook_configuration_delete_api.delete_porting_webhook_configuration_delete` · `numbers_v1_porting_webhook_configuration_fetch_api.fetch_porting_webhook_configuration_fetch` · `numbers_v1_signing_request_configuration_api.create_signing_request_configuration` · `numbers_v1_signing_request_configuration_api.list_signing_request_configuration` · `numbers_v2_authorization_document_api.create_authorization_document` · `numbers_v2_authorization_document_api.delete_authorization_document` · `numbers_v2_authorization_document_api.fetch_authorization_document` · `numbers_v2_authorization_document_api.list_authorization_document` · `numbers_v2_bulk_hosted_number_order_api.create_bulk_hosted_number_order` · `numbers_v2_bulk_hosted_number_order_api.fetch_bulk_hosted_number_order` · `numbers_v2_bundle.create_bundle` · `numbers_v2_bundle.delete_bundle` · `numbers_v2_bundle.fetch_bundle` · `numbers_v2_bundle.list_bundle` · `numbers_v2_bundle.update_bundle` · `numbers_v2_bundle_clone_api.create_bundle_clone` · `numbers_v2_bundle_copy.create_bundle_copy` · `numbers_v2_bundle_copy.list_bundle_copy` · `numbers_v2_dependent_hosted_number_order.list_dependent_hosted_number_order` · `numbers_v2_end_user.create_end_user` · `numbers_v2_end_user.delete_end_user` · `numbers_v2_end_user.fetch_end_user` · `numbers_v2_end_user.list_end_user` · `numbers_v2_end_user.update_end_user` · `numbers_v2_end_user_type.fetch_end_user_type` · `numbers_v2_end_user_type.list_end_user_type` · `numbers_v2_evaluation.create_evaluation` · `numbers_v2_evaluation.fetch_evaluation` · `numbers_v2_evaluation.list_evaluation` · `numbers_v2_hosted_number_order_api.create_hosted_number_order` · `numbers_v2_hosted_number_order_api.delete_hosted_number_order` · `numbers_v2_hosted_number_order_api.fetch_hosted_number_order` · `numbers_v2_hosted_number_order_api.list_hosted_number_order` · `numbers_v2_hosted_number_order_api.update_hosted_number_order` · `numbers_v2_item_assignment.create_item_assignment` · `numbers_v2_item_assignment.delete_item_assignment` · `numbers_v2_item_assignment.fetch_item_assignment` · `numbers_v2_item_assignment.list_item_assignment` · `numbers_v2_regulation.fetch_regulation` · `numbers_v2_regulation.list_regulation` · `numbers_v2_replace_items.create_replace_items` · `numbers_v2_supporting_document.create_supporting_document` · `numbers_v2_supporting_document.delete_supporting_document` · `numbers_v2_supporting_document.fetch_supporting_document` · `numbers_v2_supporting_document.list_supporting_document` · `numbers_v2_supporting_document.update_supporting_document` · `numbers_v2_supporting_document_type.fetch_supporting_document_type` · `numbers_v2_supporting_document_type.list_supporting_document_type` · `numbers_v3_hosted_numbers_hosted_number_order_api.create_hosted_numbers_hosted_number_order` · `proxy_v1_interaction.delete_interaction` · `proxy_v1_interaction.fetch_interaction` · `proxy_v1_interaction.list_interaction` · `proxy_v1_message_interaction.create_message_interaction` · `proxy_v1_message_interaction.fetch_message_interaction` · `proxy_v1_message_interaction.list_message_interaction` · `proxy_v1_participant.create_participant2` · `proxy_v1_participant.delete_participant2` · `proxy_v1_participant.fetch_participant3` · `proxy_v1_participant.list_participant2` · `proxy_v1_phone_number.create_phone_number2` · `proxy_v1_phone_number.delete_phone_number2` · `proxy_v1_phone_number.fetch_phone_number4` · `proxy_v1_phone_number.list_phone_number2` · `proxy_v1_phone_number.update_phone_number` · `proxy_v1_service_api.create_service4` · `proxy_v1_service_api.delete_service4` · `proxy_v1_service_api.fetch_service4` · `proxy_v1_service_api.list_service4` · `proxy_v1_service_api.update_service3` · `proxy_v1_session.create_session` · `proxy_v1_session.delete_session` · `proxy_v1_session.fetch_session` · `proxy_v1_session.list_session` · `proxy_v1_session.update_session` · `studio_v1_engagement.create_engagement` · `studio_v1_engagement.delete_engagement` · `studio_v1_engagement.fetch_engagement` · `studio_v1_engagement.list_engagement` · `studio_v1_engagement_context.fetch_engagement_context` · `studio_v1_execution.create_execution` · `studio_v1_execution.delete_execution` · `studio_v1_execution.fetch_execution` · `studio_v1_execution.list_execution` · `studio_v1_execution.update_execution` · `studio_v1_execution_context.fetch_execution_context` · `studio_v1_execution_step.fetch_execution_step` · `studio_v1_execution_step.list_execution_step` · `studio_v1_execution_step_context.fetch_execution_step_context` · `studio_v1_flow_api.delete_flow` · `studio_v1_flow_api.fetch_flow` · `studio_v1_flow_api.list_flow` · `studio_v1_step.fetch_step` · `studio_v1_step.list_step` · `studio_v1_step_context.fetch_step_context` · `studio_v2_execution.create_execution2` · `studio_v2_execution.delete_execution2` · `studio_v2_execution.fetch_execution2` · `studio_v2_execution.list_execution2` · `studio_v2_execution.update_execution2` · `studio_v2_execution_context.fetch_execution_context2` · `studio_v2_execution_step.fetch_execution_step2` · `studio_v2_execution_step.list_execution_step2` · `studio_v2_execution_step_context.fetch_execution_step_context2` · `studio_v2_flow_api.create_flow` · `studio_v2_flow_api.delete_flow2` · `studio_v2_flow_api.fetch_flow2` · `studio_v2_flow_api.list_flow2` · `studio_v2_flow_api.update_flow` · `studio_v2_flow_revision.fetch_flow_revision` · `studio_v2_flow_revision.list_flow_revision` · `studio_v2_flow_test_user_api.fetch_test_user` · `studio_v2_flow_test_user_api.update_test_user` · `studio_v2_flow_validate_api.update_flow_validate` · `sync_v1_document.create_document` · `sync_v1_document.delete_document` · `sync_v1_document.fetch_document` · `sync_v1_document.list_document` · `sync_v1_document.update_document` · `sync_v1_document_permission.delete_document_permission` · `sync_v1_document_permission.fetch_document_permission` · `sync_v1_document_permission.list_document_permission` · `sync_v1_document_permission.update_document_permission` · `sync_v1_service_api.create_service5` · `sync_v1_service_api.delete_service5` · `sync_v1_service_api.fetch_service5` · `sync_v1_service_api.list_service5` · `sync_v1_service_api.update_service4` · `sync_v1_stream_message.create_stream_message` · `sync_v1_sync_list.create_sync_list` · `sync_v1_sync_list.delete_sync_list` · `sync_v1_sync_list.fetch_sync_list` · `sync_v1_sync_list.list_sync_list` · `sync_v1_sync_list.update_sync_list` · `sync_v1_sync_list_item.create_sync_list_item` · `sync_v1_sync_list_item.delete_sync_list_item` · `sync_v1_sync_list_item.fetch_sync_list_item` · `sync_v1_sync_list_item.list_sync_list_item` · `sync_v1_sync_list_item.update_sync_list_item` · `sync_v1_sync_list_permission.delete_sync_list_permission` · `sync_v1_sync_list_permission.fetch_sync_list_permission` · `sync_v1_sync_list_permission.list_sync_list_permission` · `sync_v1_sync_list_permission.update_sync_list_permission` · `sync_v1_sync_map.create_sync_map` · `sync_v1_sync_map.delete_sync_map` · `sync_v1_sync_map.fetch_sync_map` · `sync_v1_sync_map.list_sync_map` · `sync_v1_sync_map.update_sync_map` · `sync_v1_sync_map_item.create_sync_map_item` · `sync_v1_sync_map_item.delete_sync_map_item` · `sync_v1_sync_map_item.fetch_sync_map_item` · `sync_v1_sync_map_item.list_sync_map_item` · `sync_v1_sync_map_item.update_sync_map_item` · `sync_v1_sync_map_permission.delete_sync_map_permission` · `sync_v1_sync_map_permission.fetch_sync_map_permission` · `sync_v1_sync_map_permission.list_sync_map_permission` · `sync_v1_sync_map_permission.update_sync_map_permission` · `sync_v1_sync_stream.create_sync_stream` · `sync_v1_sync_stream.delete_sync_stream` · `sync_v1_sync_stream.fetch_sync_stream` · `sync_v1_sync_stream.list_sync_stream` · `sync_v1_sync_stream.update_sync_stream` · `taskrouter_v1_activity.create_activity` · `taskrouter_v1_activity.delete_activity` · `taskrouter_v1_activity.fetch_activity` · `taskrouter_v1_activity.list_activity` · `taskrouter_v1_activity.update_activity` · `taskrouter_v1_event.fetch_event` · `taskrouter_v1_event.list_event` · `taskrouter_v1_task.create_task` · `taskrouter_v1_task.delete_task` · `taskrouter_v1_task.fetch_task` · `taskrouter_v1_task.list_task` · `taskrouter_v1_task.update_task` · `taskrouter_v1_task_channel.create_task_channel` · `taskrouter_v1_task_channel.delete_task_channel` · `taskrouter_v1_task_channel.fetch_task_channel` · `taskrouter_v1_task_channel.list_task_channel` · `taskrouter_v1_task_channel.update_task_channel` · `taskrouter_v1_task_queue.create_task_queue` · `taskrouter_v1_task_queue.delete_task_queue` · `taskrouter_v1_task_queue.fetch_task_queue` · `taskrouter_v1_task_queue.list_task_queue` · `taskrouter_v1_task_queue.update_task_queue` · `taskrouter_v1_task_queue_bulk_real_time_statistics.create_task_queue_bulk_real_time_statistics` · `taskrouter_v1_task_queue_cumulative_statistics.fetch_task_queue_cumulative_statistics` · `taskrouter_v1_task_queue_real_time_statistics.fetch_task_queue_real_time_statistics` · `taskrouter_v1_task_queue_statistics.fetch_task_queue_statistics` · `taskrouter_v1_task_queues_statistics.list_task_queues_statistics` · `taskrouter_v1_task_reservation.fetch_task_reservation` · `taskrouter_v1_task_reservation.list_task_reservation` · `taskrouter_v1_task_reservation.update_task_reservation` · `taskrouter_v1_worker.create_worker` · `taskrouter_v1_worker.delete_worker` · `taskrouter_v1_worker.fetch_worker` · `taskrouter_v1_worker.list_worker` · `taskrouter_v1_worker.update_worker` · `taskrouter_v1_worker_channel.fetch_worker_channel` · `taskrouter_v1_worker_channel.list_worker_channel` · `taskrouter_v1_worker_channel.update_worker_channel` · `taskrouter_v1_worker_reservation.fetch_worker_reservation` · `taskrouter_v1_worker_reservation.list_worker_reservation` · `taskrouter_v1_worker_reservation.update_worker_reservation` · `taskrouter_v1_worker_statistics.fetch_worker_instance_statistics` · `taskrouter_v1_workers_cumulative_statistics.fetch_workers_cumulative_statistics` · `taskrouter_v1_workers_real_time_statistics.fetch_workers_real_time_statistics` · `taskrouter_v1_workers_statistics.fetch_worker_statistics` · `taskrouter_v1_workflow.create_workflow` · `taskrouter_v1_workflow.delete_workflow` · `taskrouter_v1_workflow.fetch_workflow` · `taskrouter_v1_workflow.list_workflow` · `taskrouter_v1_workflow.update_workflow` · `taskrouter_v1_workflow_cumulative_statistics.fetch_workflow_cumulative_statistics` · `taskrouter_v1_workflow_real_time_statistics.fetch_workflow_real_time_statistics` · `taskrouter_v1_workflow_statistics.fetch_workflow_statistics` · `taskrouter_v1_workspace_api.create_workspace` · `taskrouter_v1_workspace_api.delete_workspace` · `taskrouter_v1_workspace_api.fetch_workspace` · `taskrouter_v1_workspace_api.list_workspace` · `taskrouter_v1_workspace_api.update_workspace` · `taskrouter_v1_workspace_cumulative_statistics.fetch_workspace_cumulative_statistics` · `taskrouter_v1_workspace_real_time_statistics.fetch_workspace_real_time_statistics` · `taskrouter_v1_workspace_statistics.fetch_workspace_statistics` · `trusthub_v1_compliance_inquiries.create_compliance_inquiry` · `trusthub_v1_compliance_inquiries.update_compliance_inquiry` · `trusthub_v1_compliance_registration_inquiries.create_compliance_registration` · `trusthub_v1_compliance_registration_inquiries.update_compliance_registration` · `trusthub_v1_compliance_tollfree_inquiries.create_compliance_tollfree_inquiry` · `trusthub_v1_customer_profiles.create_customer_profile` · `trusthub_v1_customer_profiles.delete_customer_profile` · `trusthub_v1_customer_profiles.fetch_customer_profile` · `trusthub_v1_customer_profiles.list_customer_profile` · `trusthub_v1_customer_profiles.update_customer_profile` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.create_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.delete_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.fetch_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.list_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_entity_assignments.create_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_entity_assignments.delete_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_entity_assignments.fetch_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_entity_assignments.list_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_evaluations.create_customer_profile_evaluation` · `trusthub_v1_customer_profiles_evaluations.fetch_customer_profile_evaluation` · `trusthub_v1_customer_profiles_evaluations.list_customer_profile_evaluation` · `trusthub_v1_end_user_api.create_end_user2` · `trusthub_v1_end_user_api.delete_end_user2` · `trusthub_v1_end_user_api.fetch_end_user2` · `trusthub_v1_end_user_api.list_end_user2` · `trusthub_v1_end_user_api.update_end_user2` · `trusthub_v1_end_user_type.fetch_end_user_type2` · `trusthub_v1_end_user_type.list_end_user_type2` · `trusthub_v1_policies_api.fetch_policies` · `trusthub_v1_policies_api.list_policies` · `trusthub_v1_supporting_document_api.create_supporting_document2` · `trusthub_v1_supporting_document_api.delete_supporting_document2` · `trusthub_v1_supporting_document_api.fetch_supporting_document2` · `trusthub_v1_supporting_document_api.list_supporting_document2` · `trusthub_v1_supporting_document_api.update_supporting_document2` · `trusthub_v1_supporting_document_type.fetch_supporting_document_type2` · `trusthub_v1_supporting_document_type.list_supporting_document_type2` · `trusthub_v1_trust_products.create_trust_product` · `trusthub_v1_trust_products.delete_trust_product` · `trusthub_v1_trust_products.fetch_trust_product` · `trusthub_v1_trust_products.list_trust_product` · `trusthub_v1_trust_products.update_trust_product` · `trusthub_v1_trust_products_channel_endpoint_assignment.create_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_channel_endpoint_assignment.delete_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_channel_endpoint_assignment.fetch_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_channel_endpoint_assignment.list_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_entity_assignments.create_trust_product_entity_assignment` · `trusthub_v1_trust_products_entity_assignments.delete_trust_product_entity_assignment` · `trusthub_v1_trust_products_entity_assignments.fetch_trust_product_entity_assignment` · `trusthub_v1_trust_products_entity_assignments.list_trust_product_entity_assignment` · `trusthub_v1_trust_products_evaluations.create_trust_product_evaluation` · `trusthub_v1_trust_products_evaluations.fetch_trust_product_evaluation` · `trusthub_v1_trust_products_evaluations.list_trust_product_evaluation` · `v2_short_code_applications.create_short_code_application` · `v2_short_code_applications.fetch_short_code_application` · `v2_short_code_applications.list_short_code_applications` · `verify_v2_access_token.create_access_token` · `verify_v2_access_token.fetch_access_token` · `verify_v2_bucket.create_bucket` · `verify_v2_bucket.delete_bucket` · `verify_v2_bucket.fetch_bucket` · `verify_v2_bucket.list_bucket` · `verify_v2_bucket.update_bucket` · `verify_v2_challenge.create_challenge` · `verify_v2_challenge.fetch_challenge` · `verify_v2_challenge.list_challenge` · `verify_v2_challenge.update_challenge` · `verify_v2_entity.create_entity` · `verify_v2_entity.delete_entity` · `verify_v2_entity.fetch_entity` · `verify_v2_entity.list_entity` · `verify_v2_factor.delete_factor` · `verify_v2_factor.fetch_factor` · `verify_v2_factor.list_factor` · `verify_v2_factor.update_factor` · `verify_v2_form_api.fetch_form` · `verify_v2_messaging_configuration.create_messaging_configuration` · `verify_v2_messaging_configuration.delete_messaging_configuration` · `verify_v2_messaging_configuration.fetch_messaging_configuration` · `verify_v2_messaging_configuration.list_messaging_configuration` · `verify_v2_messaging_configuration.update_messaging_configuration` · `verify_v2_new_challenge.create_challenge_passkeys` · `verify_v2_new_factor.create_new_factor` · `verify_v2_new_factor.create_new_factor_passkey` · `verify_v2_notification.create_notification` · `verify_v2_rate_limit.create_rate_limit` · `verify_v2_rate_limit.delete_rate_limit` · `verify_v2_rate_limit.fetch_rate_limit` · `verify_v2_rate_limit.list_rate_limit` · `verify_v2_rate_limit.update_rate_limit` · `verify_v2_safelist_api.create_safelist` · `verify_v2_safelist_api.delete_safelist` · `verify_v2_safelist_api.fetch_safelist` · `verify_v2_service_api.create_service2` · `verify_v2_service_api.delete_service2` · `verify_v2_service_api.fetch_service2` · `verify_v2_service_api.list_service2` · `verify_v2_service_api.update_service2` · `verify_v2_template.list_verification_template` · `verify_v2_verification.fetch_verification` · `verify_v2_verification.update_verification` · `verify_v2_verification_attempt_api.fetch_verification_attempt` · `verify_v2_verification_attempt_api.list_verification_attempt` · `verify_v2_verification_attempts_summary_api.fetch_verification_attempts_summary` · `verify_v2_verification_check.create_verification_check` · `verify_v2_webhook.create_webhook` · `verify_v2_webhook.delete_webhook` · `verify_v2_webhook.fetch_webhook` · `verify_v2_webhook.list_webhook` · `verify_v2_webhook.update_webhook` · `video_v1_anonymize.update_room_participant_anonymize` · `video_v1_composition_api.create_composition` · `video_v1_composition_api.delete_composition` · `video_v1_composition_api.fetch_composition` · `video_v1_composition_api.list_composition` · `video_v1_composition_hook_api.create_composition_hook` · `video_v1_composition_hook_api.delete_composition_hook` · `video_v1_composition_hook_api.fetch_composition_hook` · `video_v1_composition_hook_api.list_composition_hook` · `video_v1_composition_hook_api.update_composition_hook` · `video_v1_composition_settings_api.create_composition_settings` · `video_v1_composition_settings_api.fetch_composition_settings` · `video_v1_participant.fetch_room_participant` · `video_v1_participant.list_room_participant` · `video_v1_participant.update_room_participant` · `video_v1_published_track.fetch_room_participant_published_track` · `video_v1_published_track.list_room_participant_published_track` · `video_v1_recording_api.delete_recording2` · `video_v1_recording_api.fetch_recording2` · `video_v1_recording_api.list_recording2` · `video_v1_recording_rules.fetch_room_recording_rule` · `video_v1_recording_rules.update_room_recording_rule` · `video_v1_recording_settings_api.create_recording_settings` · `video_v1_recording_settings_api.fetch_recording_settings` · `video_v1_room_api.create_room` · `video_v1_room_api.fetch_room` · `video_v1_room_api.list_room` · `video_v1_room_api.update_room` · `video_v1_room_recording.delete_room_recording` · `video_v1_room_recording.fetch_room_recording` · `video_v1_room_recording.list_room_recording` · `video_v1_subscribe_rules.fetch_room_participant_subscribe_rule` · `video_v1_subscribe_rules.update_room_participant_subscribe_rule` · `video_v1_subscribed_track.fetch_room_participant_subscribed_track` · `video_v1_subscribed_track.list_room_participant_subscribed_track` · `video_v1_transcriptions.create_room_transcriptions` · `video_v1_transcriptions.fetch_room_transcriptions` · `video_v1_transcriptions.list_room_transcriptions` · `video_v1_transcriptions.update_room_transcriptions`: no typed arm, so `.error` is always `RawError` - 9. So `isinstance(e.error, AccountsCallsRecordingsSidJson201041408Error1)` matches only 34 of 898 operations. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 23 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which server each call resolves against**, set through `server_config`. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Twilio SDK Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Twilio SDK Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Twilio SDK Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Twilio SDK Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-twilio-sdk` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `twilio_sdk/` and `pyproject.toml` of the generated package at version `1.0.0`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Twilio SDK | +| Distribution name (what you install) | `twilio-sdk` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `twilio_sdk` — not the string you install | +| Source repository | https://github.com/context-plugins/twilio-python-sdk | +| Source branch | `main` | +| Version | `1.0.0` | +| Sync client class | `TwilioSdkClient` (alias `Client`) | +| Async client class | `AsyncTwilioSdkClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `server_config` · `timeout` (default `30.0`) · `retry_options` · `account_sid_auth_token`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | HTTP **Basic** — set `account_sid_auth_token` | +| Environments | one environment; 15 named servers configured through `server_config` | +| Base-URL config | `ServerConfig` (`twilio_sdk/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 898 operations across 318 controllers, plus 11 directly on the client (`map/operations/client.md`) · 838 models · 4 unions · 399 enums · 37 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "twilio-sdk @ git+https://github.com/context-plugins/twilio-python-sdk.git@main" +python -c "import twilio_sdk, pathlib; print(pathlib.Path(twilio_sdk.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from twilio_sdk import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`twilio_sdk/__init__.py` exports exactly 7 names beside the `twilio_sdk.models` subpackage it re-exports: + +```python +from twilio_sdk import ( + AsyncClient, + AsyncTwilioSdkClient, + Client, + ServerConfig, + ServerConfigDict, + ServerConfigOrDict, + TwilioSdkClient, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `twilio_sdk.models` | +| Enums (and their open `…OrStr` aliases) | `twilio_sdk.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `BasicAuthCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `twilio_sdk.core` | +| Per-operation error *unions* | `twilio_sdk.errors` (`CreateCommunicationInConversationErrorBody`, …) | + +`twilio_sdk.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Servers — one config object, several servers + +The API declares 15 named servers and a single environment, so the constructor takes `server_config: ServerConfigOrDict | None = None` rather than a bare `base_url`. Each server is a field on that config: + +| Server field | Base URL | +| --- | --- | +| `default` | `https://api.twilio.com` | +| `default1` | `https://messaging.twilio.com` | +| `default2` | `https://content.twilio.com` | +| `default3` | `https://verify.twilio.com` | +| `default4` | `https://lookups.twilio.com` | +| `default5` | `https://numbers.twilio.com` | +| `default6` | `https://video.twilio.com` | +| `default7` | `https://conversations.twilio.com` | +| `default8` | `https://taskrouter.twilio.com` | +| `default9` | `https://trusthub.twilio.com` | +| `default10` | `https://proxy.twilio.com` | +| `default11` | `https://studio.twilio.com` | +| `default12` | `https://sync.twilio.com` | +| `default13` | `https://flex-api.twilio.com` | +| `default14` | `https://insights.twilio.com` | + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `server_config` falls through to the config's own defaults rather than writing `None` over them. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (one scheme) + +HTTP Basic authentication, exposed as the client's `account_sid_auth_token=` keyword taking `BasicAuthCredentials` **or a plain dict**. + +```python +from twilio_sdk import Client +from twilio_sdk.core import BasicAuthCredentials + +client = Client(account_sid_auth_token=BasicAuthCredentials(username="…", password="…")) +client = Client(account_sid_auth_token={"username": "…", "password": "…"}) # equivalent +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.api20100401_account` | `Api20100401Account` / `AsyncApi20100401Account` | 4 | `create_account` · `fetch_account` · `list_account` · `update_account` | +| `client.api20100401_add_on_result` | `Api20100401AddOnResult` / `AsyncApi20100401AddOnResult` | 3 | `delete_recording_add_on_result` · `fetch_recording_add_on_result` · `list_recording_add_on_result` | +| `client.api20100401_address` | `Api20100401Address` / `AsyncApi20100401Address` | 5 | `create_address` · `delete_address` · `fetch_address` · `list_address` · `update_address` | +| `client.api20100401_all_time` | `Api20100401AllTime` / `AsyncApi20100401AllTime` | 1 | `list_usage_record_all_time` | +| `client.api20100401_application` | `Api20100401Application` / `AsyncApi20100401Application` | 5 | `create_application` · `delete_application` · `fetch_application` · `list_application` · `update_application` | +| `client.api20100401_assigned_add_on` | `Api20100401AssignedAddOn` / `AsyncApi20100401AssignedAddOn` | 4 | `create_incoming_phone_number_assigned_add_on` · `delete_incoming_phone_number_assigned_add_on` · `fetch_incoming_phone_number_assigned_add_on` · `list_incoming_phone_number_assigned_add_on` | +| `client.api20100401_assigned_add_on_extension` | `Api20100401AssignedAddOnExtension` / `AsyncApi20100401AssignedAddOnExtension` | 2 | `fetch_incoming_phone_number_assigned_add_on_extension` · `list_incoming_phone_number_assigned_add_on_extension` | +| `client.api20100401_auth_calls_credential_list_mapping` | `Api20100401AuthCallsCredentialListMapping` / `AsyncApi20100401AuthCallsCredentialListMapping` | 4 | `create_sip_auth_calls_credential_list_mapping` · `delete_sip_auth_calls_credential_list_mapping` · `fetch_sip_auth_calls_credential_list_mapping` · `list_sip_auth_calls_credential_list_mapping` | +| `client.api20100401_auth_calls_ip_access_control_list_mapping` | `Api20100401AuthCallsIpAccessControlListMapping` / `AsyncApi20100401AuthCallsIpAccessControlListMapping` | 4 | `create_sip_auth_calls_ip_access_control_list_mapping` · `delete_sip_auth_calls_ip_access_control_list_mapping` · `fetch_sip_auth_calls_ip_access_control_list_mapping` · `list_sip_auth_calls_ip_access_control_list_mapping` | +| `client.api20100401_auth_registrations_credential_list_mapping` | `Api20100401AuthRegistrationsCredentialListMapping` / `AsyncApi20100401AuthRegistrationsCredentialListMapping` | 4 | `create_sip_auth_registrations_credential_list_mapping` · `delete_sip_auth_registrations_credential_list_mapping` · `fetch_sip_auth_registrations_credential_list_mapping` · `list_sip_auth_registrations_credential_list_mapping` | +| `client.api20100401_authorized_connect_app` | `Api20100401AuthorizedConnectApp` / `AsyncApi20100401AuthorizedConnectApp` | 2 | `fetch_authorized_connect_app` · `list_authorized_connect_app` | +| `client.api20100401_available_phone_number_country` | `Api20100401AvailablePhoneNumberCountry` / `AsyncApi20100401AvailablePhoneNumberCountry` | 2 | `fetch_available_phone_number_country` · `list_available_phone_number_country` | +| `client.api20100401_balance` | `Api20100401Balance` / `AsyncApi20100401Balance` | 1 | `fetch_balance` | +| `client.api20100401_call` | `Api20100401Call` / `AsyncApi20100401Call` | 5 | `create_call` · `delete_call` · `fetch_call` · `list_call` · `update_call` | +| `client.api20100401_call_notification` | `Api20100401CallNotification` / `AsyncApi20100401CallNotification` | 2 | `fetch_call_notification` · `list_call_notification` | +| `client.api20100401_call_recording` | `Api20100401CallRecording` / `AsyncApi20100401CallRecording` | 5 | `create_call_recording` · `delete_call_recording` · `fetch_call_recording` · `list_call_recording` · `update_call_recording` | +| `client.api20100401_call_transcription` | `Api20100401CallTranscription` / `AsyncApi20100401CallTranscription` | 2 | `create_realtime_transcription` · `update_realtime_transcription` | +| `client.api20100401_conference` | `Api20100401Conference` / `AsyncApi20100401Conference` | 3 | `fetch_conference` · `list_conference` · `update_conference` | +| `client.api20100401_conference_recording` | `Api20100401ConferenceRecording` / `AsyncApi20100401ConferenceRecording` | 4 | `delete_conference_recording` · `fetch_conference_recording` · `list_conference_recording` · `update_conference_recording` | +| `client.api20100401_connect_app` | `Api20100401ConnectApp` / `AsyncApi20100401ConnectApp` | 4 | `delete_connect_app` · `fetch_connect_app` · `list_connect_app` · `update_connect_app` | +| `client.api20100401_credential` | `Api20100401Credential` / `AsyncApi20100401Credential` | 5 | `create_sip_credential` · `delete_sip_credential` · `fetch_sip_credential` · `list_sip_credential` · `update_sip_credential` | +| `client.api20100401_credential_list` | `Api20100401CredentialList` / `AsyncApi20100401CredentialList` | 5 | `create_sip_credential_list` · `delete_sip_credential_list` · `fetch_sip_credential_list` · `list_sip_credential_list` · `update_sip_credential_list` | +| `client.api20100401_credential_list_mapping` | `Api20100401CredentialListMapping` / `AsyncApi20100401CredentialListMapping` | 4 | `create_sip_credential_list_mapping` · `delete_sip_credential_list_mapping` · `fetch_sip_credential_list_mapping` · `list_sip_credential_list_mapping` | +| `client.api20100401_daily` | `Api20100401Daily` / `AsyncApi20100401Daily` | 1 | `list_usage_record_daily` | +| `client.api20100401_data` | `Api20100401Data` / `AsyncApi20100401Data` | 1 | `fetch_recording_add_on_result_payload_data` | +| `client.api20100401_dependent_phone_number` | `Api20100401DependentPhoneNumber` / `AsyncApi20100401DependentPhoneNumber` | 1 | `list_dependent_phone_number` | +| `client.api20100401_domain` | `Api20100401Domain` / `AsyncApi20100401Domain` | 5 | `create_sip_domain` · `delete_sip_domain` · `fetch_sip_domain` · `list_sip_domain` · `update_sip_domain` | +| `client.api20100401_event` | `Api20100401Event` / `AsyncApi20100401Event` | 1 | `list_call_event` | +| `client.api20100401_feedback` | `Api20100401Feedback` / `AsyncApi20100401Feedback` | 1 | `create_message_feedback` | +| `client.api20100401_incoming_phone_number` | `Api20100401IncomingPhoneNumber` / `AsyncApi20100401IncomingPhoneNumber` | 5 | `create_incoming_phone_number` · `delete_incoming_phone_number` · `fetch_incoming_phone_number` · `list_incoming_phone_number` · `update_incoming_phone_number` | +| `client.api20100401_incoming_phone_number_local` | `Api20100401IncomingPhoneNumberLocal` / `AsyncApi20100401IncomingPhoneNumberLocal` | 2 | `create_incoming_phone_number_local` · `list_incoming_phone_number_local` | +| `client.api20100401_incoming_phone_number_mobile` | `Api20100401IncomingPhoneNumberMobile` / `AsyncApi20100401IncomingPhoneNumberMobile` | 2 | `create_incoming_phone_number_mobile` · `list_incoming_phone_number_mobile` | +| `client.api20100401_incoming_phone_number_toll_free` | `Api20100401IncomingPhoneNumberTollFree` / `AsyncApi20100401IncomingPhoneNumberTollFree` | 2 | `create_incoming_phone_number_toll_free` · `list_incoming_phone_number_toll_free` | +| `client.api20100401_ip_access_control_list` | `Api20100401IpAccessControlList` / `AsyncApi20100401IpAccessControlList` | 5 | `create_sip_ip_access_control_list` · `delete_sip_ip_access_control_list` · `fetch_sip_ip_access_control_list` · `list_sip_ip_access_control_list` · `update_sip_ip_access_control_list` | +| `client.api20100401_ip_access_control_list_mapping` | `Api20100401IpAccessControlListMapping` / `AsyncApi20100401IpAccessControlListMapping` | 4 | `create_sip_ip_access_control_list_mapping` · `delete_sip_ip_access_control_list_mapping` · `fetch_sip_ip_access_control_list_mapping` · `list_sip_ip_access_control_list_mapping` | +| `client.api20100401_key` | `Api20100401Key` / `AsyncApi20100401Key` | 4 | `delete_key` · `fetch_key` · `list_key` · `update_key` | +| `client.api20100401_last_month` | `Api20100401LastMonth` / `AsyncApi20100401LastMonth` | 1 | `list_usage_record_last_month` | +| `client.api20100401_local` | `Api20100401Local` / `AsyncApi20100401Local` | 1 | `list_available_phone_number_local` | +| `client.api20100401_machine_to_machine` | `Api20100401MachineToMachine` / `AsyncApi20100401MachineToMachine` | 1 | `list_available_phone_number_machine_to_machine` | +| `client.api20100401_media` | `Api20100401Media` / `AsyncApi20100401Media` | 1 | `list_media` | +| `client.api20100401_media_instance` | `Api20100401MediaInstance` / `AsyncApi20100401MediaInstance` | 2 | `delete_media` · `fetch_media` | +| `client.api20100401_member` | `Api20100401Member` / `AsyncApi20100401Member` | 3 | `fetch_member` · `list_member` · `update_member` | +| `client.api20100401_message` | `Api20100401Message` / `AsyncApi20100401Message` | 5 | `create_message` · `delete_message` · `fetch_message` · `list_message` · `update_message` | +| `client.api20100401_mobile` | `Api20100401Mobile` / `AsyncApi20100401Mobile` | 1 | `list_available_phone_number_mobile` | +| `client.api20100401_monthly` | `Api20100401Monthly` / `AsyncApi20100401Monthly` | 1 | `list_usage_record_monthly` | +| `client.api20100401_national` | `Api20100401National` / `AsyncApi20100401National` | 1 | `list_available_phone_number_national` | +| `client.api20100401_new_key` | `Api20100401NewKey` / `AsyncApi20100401NewKey` | 1 | `create_new_key` | +| `client.api20100401_new_signing_key` | `Api20100401NewSigningKey` / `AsyncApi20100401NewSigningKey` | 1 | `create_new_signing_key` | +| `client.api20100401_notification` | `Api20100401Notification` / `AsyncApi20100401Notification` | 2 | `fetch_notification` · `list_notification` | +| `client.api20100401_outgoing_caller_id` | `Api20100401OutgoingCallerId` / `AsyncApi20100401OutgoingCallerId` | 4 | `delete_outgoing_caller_id` · `fetch_outgoing_caller_id` · `list_outgoing_caller_id` · `update_outgoing_caller_id` | +| `client.api20100401_participant` | `Api20100401Participant` / `AsyncApi20100401Participant` | 5 | `create_participant` · `delete_participant` · `fetch_participant` · `list_participant` · `update_participant` | +| `client.api20100401_payload` | `Api20100401Payload` / `AsyncApi20100401Payload` | 3 | `delete_recording_add_on_result_payload` · `fetch_recording_add_on_result_payload` · `list_recording_add_on_result_payload` | +| `client.api20100401_payment` | `Api20100401Payment` / `AsyncApi20100401Payment` | 2 | `create_payments` · `update_payments` | +| `client.api20100401_queue` | `Api20100401Queue` / `AsyncApi20100401Queue` | 5 | `create_queue` · `delete_queue` · `fetch_queue` · `list_queue` · `update_queue` | +| `client.api20100401_record` | `Api20100401Record` / `AsyncApi20100401Record` | 1 | `list_usage_record` | +| `client.api20100401_recording` | `Api20100401Recording` / `AsyncApi20100401Recording` | 3 | `delete_recording` · `fetch_recording` · `list_recording` | +| `client.api20100401_recording_transcription` | `Api20100401RecordingTranscription` / `AsyncApi20100401RecordingTranscription` | 3 | `delete_recording_transcription` · `fetch_recording_transcription` · `list_recording_transcription` | +| `client.api20100401_shared_cost` | `Api20100401SharedCost` / `AsyncApi20100401SharedCost` | 1 | `list_available_phone_number_shared_cost` | +| `client.api20100401_short_code` | `Api20100401ShortCode` / `AsyncApi20100401ShortCode` | 3 | `fetch_short_code` · `list_short_code` · `update_short_code` | +| `client.api20100401_signing_key` | `Api20100401SigningKey` / `AsyncApi20100401SigningKey` | 4 | `delete_signing_key` · `fetch_signing_key` · `list_signing_key` · `update_signing_key` | +| `client.api20100401_sip_ip_address` | `Api20100401SipIpAddress` / `AsyncApi20100401SipIpAddress` | 5 | `create_sip_ip_address` · `delete_sip_ip_address` · `fetch_sip_ip_address` · `list_sip_ip_address` · `update_sip_ip_address` | +| `client.api20100401_siprec` | `Api20100401Siprec` / `AsyncApi20100401Siprec` | 2 | `create_siprec` · `update_siprec` | +| `client.api20100401_stream` | `Api20100401Stream` / `AsyncApi20100401Stream` | 2 | `create_stream` · `update_stream` | +| `client.api20100401_this_month` | `Api20100401ThisMonth` / `AsyncApi20100401ThisMonth` | 1 | `list_usage_record_this_month` | +| `client.api20100401_today` | `Api20100401Today` / `AsyncApi20100401Today` | 1 | `list_usage_record_today` | +| `client.api20100401_token` | `Api20100401Token` / `AsyncApi20100401Token` | 1 | `create_token` | +| `client.api20100401_toll_free` | `Api20100401TollFree` / `AsyncApi20100401TollFree` | 1 | `list_available_phone_number_toll_free` | +| `client.api20100401_transcription` | `Api20100401Transcription` / `AsyncApi20100401Transcription` | 3 | `delete_transcription` · `fetch_transcription` · `list_transcription` | +| `client.api20100401_trigger` | `Api20100401Trigger` / `AsyncApi20100401Trigger` | 5 | `create_usage_trigger` · `delete_usage_trigger` · `fetch_usage_trigger` · `list_usage_trigger` · `update_usage_trigger` | +| `client.api20100401_user_defined_message` | `Api20100401UserDefinedMessage` / `AsyncApi20100401UserDefinedMessage` | 1 | `create_user_defined_message` | +| `client.api20100401_user_defined_message_subscription` | `Api20100401UserDefinedMessageSubscription` / `AsyncApi20100401UserDefinedMessageSubscription` | 2 | `create_user_defined_message_subscription` · `delete_user_defined_message_subscription` | +| `client.api20100401_validation_request` | `Api20100401ValidationRequest` / `AsyncApi20100401ValidationRequest` | 1 | `create_validation_request` | +| `client.api20100401_voip` | `Api20100401Voip` / `AsyncApi20100401Voip` | 1 | `list_available_phone_number_voip` | +| `client.api20100401_yearly` | `Api20100401Yearly` / `AsyncApi20100401Yearly` | 1 | `list_usage_record_yearly` | +| `client.api20100401_yesterday` | `Api20100401Yesterday` / `AsyncApi20100401Yesterday` | 1 | `list_usage_record_yesterday` | +| `client.content_v2_content` | `ContentV2Content` / `AsyncContentV2Content` | 1 | `list_content2` | +| `client.content_v2_content_and_approvals` | `ContentV2ContentAndApprovals` / `AsyncContentV2ContentAndApprovals` | 1 | `list_content_and_approvals2` | +| `client.contentv1_approval_create` | `Contentv1ApprovalCreate` / `AsyncContentv1ApprovalCreate` | 1 | `create_approval_create` | +| `client.contentv1_approval_fetch` | `Contentv1ApprovalFetch` / `AsyncContentv1ApprovalFetch` | 1 | `fetch_approval_fetch` | +| `client.contentv1_content_api` | `Contentv1ContentApi` / `AsyncContentv1ContentApi` | 5 | `create_content` · `delete_content` · `fetch_content` · `list_content` · `update_content` | +| `client.contentv1_content_and_approvals_api` | `Contentv1ContentAndApprovalsApi` / `AsyncContentv1ContentAndApprovalsApi` | 1 | `list_content_and_approvals` | +| `client.contentv1_legacy_content_api` | `Contentv1LegacyContentApi` / `AsyncContentv1LegacyContentApi` | 1 | `list_legacy_content` | +| `client.conversations_v1_address_configuration` | `ConversationsV1AddressConfiguration` / `AsyncConversationsV1AddressConfiguration` | 5 | `create_configuration_address` · `delete_configuration_address` · `fetch_configuration_address` · `list_configuration_address` · `update_configuration_address` | +| `client.conversations_v1_binding` | `ConversationsV1Binding` / `AsyncConversationsV1Binding` | 3 | `delete_service_binding` · `fetch_service_binding` · `list_service_binding` | +| `client.conversations_v1_configuration_api` | `ConversationsV1ConfigurationApi` / `AsyncConversationsV1ConfigurationApi` | 4 | `fetch_configuration` · `fetch_service_configuration` · `update_configuration` · `update_service_configuration` | +| `client.conversations_v1_conversation_api` | `ConversationsV1ConversationApi` / `AsyncConversationsV1ConversationApi` | 10 | `create_conversation` · `create_service_conversation` · `delete_conversation` · `delete_service_conversation` · `fetch_conversation` · `fetch_service_conversation` · … | +| `client.conversations_v1_conversation_with_participants_api` | `ConversationsV1ConversationWithParticipantsApi` / `AsyncConversationsV1ConversationWithParticipantsApi` | 2 | `create_conversation_with_participants` · `create_service_conversation_with_participants` | +| `client.conversations_v1_credential_api` | `ConversationsV1CredentialApi` / `AsyncConversationsV1CredentialApi` | 5 | `create_credential` · `delete_credential` · `fetch_credential` · `list_credential` · `update_credential` | +| `client.conversations_v1_delivery_receipt` | `ConversationsV1DeliveryReceipt` / `AsyncConversationsV1DeliveryReceipt` | 4 | `fetch_conversation_message_receipt` · `fetch_service_conversation_message_receipt` · `list_conversation_message_receipt` · `list_service_conversation_message_receipt` | +| `client.conversations_v1_message` | `ConversationsV1Message` / `AsyncConversationsV1Message` | 10 | `create_conversation_message` · `create_service_conversation_message` · `delete_conversation_message` · `delete_service_conversation_message` · `fetch_conversation_message` · `fetch_service_conversation_message` · … | +| `client.conversations_v1_notification` | `ConversationsV1Notification` / `AsyncConversationsV1Notification` | 2 | `fetch_service_notification` · `update_service_notification` | +| `client.conversations_v1_participant` | `ConversationsV1Participant` / `AsyncConversationsV1Participant` | 10 | `create_conversation_participant` · `create_service_conversation_participant` · `delete_conversation_participant` · `delete_service_conversation_participant` · `fetch_conversation_participant` · `fetch_service_conversation_participant` · … | +| `client.conversations_v1_participant_conversation_api` | `ConversationsV1ParticipantConversationApi` / `AsyncConversationsV1ParticipantConversationApi` | 2 | `list_participant_conversation` · `list_service_participant_conversation` | +| `client.conversations_v1_role_api` | `ConversationsV1RoleApi` / `AsyncConversationsV1RoleApi` | 10 | `create_role` · `create_service_role` · `delete_role` · `delete_service_role` · `fetch_role` · `fetch_service_role` · … | +| `client.conversations_v1_service_api` | `ConversationsV1ServiceApi` / `AsyncConversationsV1ServiceApi` | 4 | `create_service3` · `delete_service3` · `fetch_service3` · `list_service3` | +| `client.conversations_v1_user_api` | `ConversationsV1UserApi` / `AsyncConversationsV1UserApi` | 10 | `create_service_user` · `create_user` · `delete_service_user` · `delete_user` · `fetch_service_user` · `fetch_user` · … | +| `client.conversations_v1_user_conversation` | `ConversationsV1UserConversation` / `AsyncConversationsV1UserConversation` | 8 | `delete_service_user_conversation` · `delete_user_conversation` · `fetch_service_user_conversation` · `fetch_user_conversation` · `list_service_user_conversation` · `list_user_conversation` · … | +| `client.conversations_v1_webhook` | `ConversationsV1Webhook` / `AsyncConversationsV1Webhook` | 14 | `create_conversation_scoped_webhook` · `create_service_conversation_scoped_webhook` · `delete_conversation_scoped_webhook` · `delete_service_conversation_scoped_webhook` · `fetch_configuration_webhook` · `fetch_conversation_scoped_webhook` · … | +| `client.conversations_v2_action_api` | `ConversationsV2ActionApi` / `AsyncConversationsV2ActionApi` | 2 | `create_conversation_action` · `fetch_conversation_action` | +| `client.conversations_v2_communication_api` | `ConversationsV2CommunicationApi` / `AsyncConversationsV2CommunicationApi` | 3 | `create_communication_in_conversation` · `fetch_communication` · `list_communication_by_conversation` | +| `client.conversations_v2_configuration_api` | `ConversationsV2ConfigurationApi` / `AsyncConversationsV2ConfigurationApi` | 5 | `create_configuration` · `delete_configuration` · `fetch_configuration2` · `list_configuration` · `update_configuration2` | +| `client.conversations_v2_conversation_api` | `ConversationsV2ConversationApi` / `AsyncConversationsV2ConversationApi` | 6 | `create_conversation_with_config` · `delete_conversation_async` · `fetch_conversation2` · `list_conversation_by_account` · `patch_conversation_by_id` · `update_conversation_by_id` | +| `client.conversations_v2_operation` | `ConversationsV2Operation` / `AsyncConversationsV2Operation` | 1 | `fetch_operation_status` | +| `client.conversations_v2_participant_api` | `ConversationsV2ParticipantApi` / `AsyncConversationsV2ParticipantApi` | 4 | `create_participant_in_conversation` · `fetch_participant2` · `list_participant_by_conversation` · `update_participant_in_conversation` | +| `client.flex_v1_assessments` | `FlexV1Assessments` / `AsyncFlexV1Assessments` | 3 | `create_insights_assessments` · `list_insights_assessments` · `update_insights_assessments` | +| `client.flex_v1_channel_api` | `FlexV1ChannelApi` / `AsyncFlexV1ChannelApi` | 4 | `create_channel` · `delete_channel` · `fetch_channel` · `list_channel` | +| `client.flex_v1_configuration_api` | `FlexV1ConfigurationApi` / `AsyncFlexV1ConfigurationApi` | 2 | `fetch_configuration3` · `update_configuration3` | +| `client.flex_v1_configured_plugin` | `FlexV1ConfiguredPlugin` / `AsyncFlexV1ConfiguredPlugin` | 2 | `fetch_configured_plugin` · `list_configured_plugin` | +| `client.flex_v1_flex_flow_api` | `FlexV1FlexFlowApi` / `AsyncFlexV1FlexFlowApi` | 5 | `create_flex_flow` · `delete_flex_flow` · `fetch_flex_flow` · `list_flex_flow` · `update_flex_flow` | +| `client.flex_v1_insights_assessments_comment_api` | `FlexV1InsightsAssessmentsCommentApi` / `AsyncFlexV1InsightsAssessmentsCommentApi` | 2 | `create_insights_assessments_comment` · `list_insights_assessments_comment` | +| `client.flex_v1_insights_conversations_api` | `FlexV1InsightsConversationsApi` / `AsyncFlexV1InsightsConversationsApi` | 1 | `list_insights_conversations` | +| `client.flex_v1_insights_questionnaires_api` | `FlexV1InsightsQuestionnairesApi` / `AsyncFlexV1InsightsQuestionnairesApi` | 5 | `create_insights_questionnaires` · `delete_insights_questionnaires` · `fetch_insights_questionnaires` · `list_insights_questionnaires` · `update_insights_questionnaires` | +| `client.flex_v1_insights_questionnaires_category_api` | `FlexV1InsightsQuestionnairesCategoryApi` / `AsyncFlexV1InsightsQuestionnairesCategoryApi` | 4 | `create_insights_questionnaires_category` · `delete_insights_questionnaires_category` · `list_insights_questionnaires_category` · `update_insights_questionnaires_category` | +| `client.flex_v1_insights_questionnaires_question_api` | `FlexV1InsightsQuestionnairesQuestionApi` / `AsyncFlexV1InsightsQuestionnairesQuestionApi` | 4 | `create_insights_questionnaires_question` · `delete_insights_questionnaires_question` · `list_insights_questionnaires_question` · `update_insights_questionnaires_question` | +| `client.flex_v1_insights_segments_api` | `FlexV1InsightsSegmentsApi` / `AsyncFlexV1InsightsSegmentsApi` | 1 | `list_insights_segments` | +| `client.flex_v1_insights_session_api` | `FlexV1InsightsSessionApi` / `AsyncFlexV1InsightsSessionApi` | 1 | `create_insights_session` | +| `client.flex_v1_insights_settings_answer_sets_api` | `FlexV1InsightsSettingsAnswerSetsApi` / `AsyncFlexV1InsightsSettingsAnswerSetsApi` | 1 | `fetch_insights_settings_answersets` | +| `client.flex_v1_insights_settings_comment_api` | `FlexV1InsightsSettingsCommentApi` / `AsyncFlexV1InsightsSettingsCommentApi` | 1 | `fetch_insights_settings_comment` | +| `client.flex_v1_insights_user_roles_api` | `FlexV1InsightsUserRolesApi` / `AsyncFlexV1InsightsUserRolesApi` | 1 | `fetch_insights_user_roles` | +| `client.flex_v1_interaction_api` | `FlexV1InteractionApi` / `AsyncFlexV1InteractionApi` | 3 | `create_interaction` · `fetch_interaction2` · `update_interaction` | +| `client.flex_v1_interaction_channel` | `FlexV1InteractionChannel` / `AsyncFlexV1InteractionChannel` | 3 | `fetch_interaction_channel` · `list_interaction_channel` · `update_interaction_channel` | +| `client.flex_v1_interaction_channel_invite` | `FlexV1InteractionChannelInvite` / `AsyncFlexV1InteractionChannelInvite` | 2 | `create_interaction_channel_invite` · `list_interaction_channel_invite` | +| `client.flex_v1_interaction_channel_participant` | `FlexV1InteractionChannelParticipant` / `AsyncFlexV1InteractionChannelParticipant` | 3 | `create_interaction_channel_participant` · `list_interaction_channel_participant` · `update_interaction_channel_participant` | +| `client.flex_v1_interaction_transfer` | `FlexV1InteractionTransfer` / `AsyncFlexV1InteractionTransfer` | 3 | `create_interaction_transfer` · `fetch_interaction_transfer` · `update_interaction_transfer` | +| `client.flex_v1_plugin_api` | `FlexV1PluginApi` / `AsyncFlexV1PluginApi` | 4 | `create_plugin` · `fetch_plugin` · `list_plugin` · `update_plugin` | +| `client.flex_v1_plugin_archive_api` | `FlexV1PluginArchiveApi` / `AsyncFlexV1PluginArchiveApi` | 1 | `update_plugin_archive` | +| `client.flex_v1_plugin_configuration_api` | `FlexV1PluginConfigurationApi` / `AsyncFlexV1PluginConfigurationApi` | 3 | `create_plugin_configuration` · `fetch_plugin_configuration` · `list_plugin_configuration` | +| `client.flex_v1_plugin_configuration_archive_api` | `FlexV1PluginConfigurationArchiveApi` / `AsyncFlexV1PluginConfigurationArchiveApi` | 1 | `update_plugin_configuration_archive` | +| `client.flex_v1_plugin_release_api` | `FlexV1PluginReleaseApi` / `AsyncFlexV1PluginReleaseApi` | 3 | `create_plugin_release` · `fetch_plugin_release` · `list_plugin_release` | +| `client.flex_v1_plugin_version_archive_api` | `FlexV1PluginVersionArchiveApi` / `AsyncFlexV1PluginVersionArchiveApi` | 1 | `update_plugin_version_archive` | +| `client.flex_v1_plugin_versions` | `FlexV1PluginVersions` / `AsyncFlexV1PluginVersions` | 3 | `create_plugin_version` · `fetch_plugin_version` · `list_plugin_version` | +| `client.flex_v1_provisioning_status_api` | `FlexV1ProvisioningStatusApi` / `AsyncFlexV1ProvisioningStatusApi` | 1 | `fetch_provisioning_status` | +| `client.flex_v1_web_channel_api` | `FlexV1WebChannelApi` / `AsyncFlexV1WebChannelApi` | 5 | `create_web_channel` · `delete_web_channel` · `fetch_web_channel` · `list_web_channel` · `update_web_channel` | +| `client.flex_v2_flex_user_api` | `FlexV2FlexUserApi` / `AsyncFlexV2FlexUserApi` | 2 | `fetch_flex_user` · `update_flex_user` | +| `client.flex_v2_web_channels` | `FlexV2WebChannels` / `AsyncFlexV2WebChannels` | 1 | `create_web_channel2` | +| `client.insights_v1_annotation` | `InsightsV1Annotation` / `AsyncInsightsV1Annotation` | 2 | `fetch_annotation` · `update_annotation` | +| `client.insights_v1_call_api` | `InsightsV1CallApi` / `AsyncInsightsV1CallApi` | 1 | `fetch_call2` | +| `client.insights_v1_call_summaries_api` | `InsightsV1CallSummariesApi` / `AsyncInsightsV1CallSummariesApi` | 1 | `list_call_summaries` | +| `client.insights_v1_call_summary_api` | `InsightsV1CallSummaryApi` / `AsyncInsightsV1CallSummaryApi` | 1 | `fetch_summary` | +| `client.insights_v1_conference_api` | `InsightsV1ConferenceApi` / `AsyncInsightsV1ConferenceApi` | 2 | `fetch_conference2` · `list_conference2` | +| `client.insights_v1_conference_participant` | `InsightsV1ConferenceParticipant` / `AsyncInsightsV1ConferenceParticipant` | 2 | `fetch_conference_participant` · `list_conference_participant` | +| `client.insights_v1_create_account_report` | `InsightsV1CreateAccountReport` / `AsyncInsightsV1CreateAccountReport` | 1 | `create_account_report` | +| `client.insights_v1_create_inbound_phone_numbers_report` | `InsightsV1CreateInboundPhoneNumbersReport` / `AsyncInsightsV1CreateInboundPhoneNumbersReport` | 1 | `create_inbound_phone_numbers_report` | +| `client.insights_v1_create_outbound_phone_numbers_report` | `InsightsV1CreateOutboundPhoneNumbersReport` / `AsyncInsightsV1CreateOutboundPhoneNumbersReport` | 1 | `create_outbound_phone_numbers_report` | +| `client.insights_v1_event` | `InsightsV1Event` / `AsyncInsightsV1Event` | 1 | `list_event2` | +| `client.insights_v1_get_account_report` | `InsightsV1GetAccountReport` / `AsyncInsightsV1GetAccountReport` | 1 | `fetch_account_report` | +| `client.insights_v1_get_inbound_phone_numbers_report` | `InsightsV1GetInboundPhoneNumbersReport` / `AsyncInsightsV1GetInboundPhoneNumbersReport` | 1 | `list_inbound_phone_numbers_report` | +| `client.insights_v1_get_outbound_phone_numbers_report` | `InsightsV1GetOutboundPhoneNumbersReport` / `AsyncInsightsV1GetOutboundPhoneNumbersReport` | 1 | `list_outbound_phone_numbers_report` | +| `client.insights_v1_metric` | `InsightsV1Metric` / `AsyncInsightsV1Metric` | 1 | `list_metric` | +| `client.insights_v1_participant` | `InsightsV1Participant` / `AsyncInsightsV1Participant` | 2 | `fetch_video_participant_summary` · `list_video_participant_summary` | +| `client.insights_v1_room` | `InsightsV1Room` / `AsyncInsightsV1Room` | 2 | `fetch_video_room_summary` · `list_video_room_summary` | +| `client.insights_v1_setting` | `InsightsV1Setting` / `AsyncInsightsV1Setting` | 2 | `fetch_account_settings` · `update_account_settings` | +| `client.lookups_v1_phone_number_api` | `LookupsV1PhoneNumberApi` / `AsyncLookupsV1PhoneNumberApi` | 1 | `fetch_phone_number2` | +| `client.lookups_v2_phone_number` | `LookupsV2PhoneNumber` / `AsyncLookupsV2PhoneNumber` | 1 | `fetch_phone_number3` | +| `client.messaging_v1_alpha_sender` | `MessagingV1AlphaSender` / `AsyncMessagingV1AlphaSender` | 4 | `create_alpha_sender` · `delete_alpha_sender` · `fetch_alpha_sender` · `list_alpha_sender` | +| `client.messaging_v1_brand_registration` | `MessagingV1BrandRegistration` / `AsyncMessagingV1BrandRegistration` | 4 | `create_brand_registrations` · `fetch_brand_registrations` · `list_brand_registrations` · `update_brand_registrations` | +| `client.messaging_v1_brand_registration_otp` | `MessagingV1BrandRegistrationOtp` / `AsyncMessagingV1BrandRegistrationOtp` | 1 | `create_brand_registration_otp` | +| `client.messaging_v1_brand_vetting` | `MessagingV1BrandVetting` / `AsyncMessagingV1BrandVetting` | 3 | `create_brand_vetting` · `fetch_brand_vetting` · `list_brand_vetting` | +| `client.messaging_v1_channel_sender` | `MessagingV1ChannelSender` / `AsyncMessagingV1ChannelSender` | 4 | `create_channel_sender` · `delete_channel_sender` · `fetch_channel_sender` · `list_channel_sender` | +| `client.messaging_v1_deactivations` | `MessagingV1Deactivations` / `AsyncMessagingV1Deactivations` | 1 | `fetch_deactivation` | +| `client.messaging_v1_destination_alpha_sender` | `MessagingV1DestinationAlphaSender` / `AsyncMessagingV1DestinationAlphaSender` | 4 | `create_destination_alpha_sender` · `delete_destination_alpha_sender` · `fetch_destination_alpha_sender` · `list_destination_alpha_sender` | +| `client.messaging_v1_domain_certs` | `MessagingV1DomainCerts` / `AsyncMessagingV1DomainCerts` | 3 | `delete_domain_cert_v4` · `fetch_domain_cert_v4` · `update_domain_cert_v4` | +| `client.messaging_v1_domain_config_api` | `MessagingV1DomainConfigApi` / `AsyncMessagingV1DomainConfigApi` | 2 | `fetch_domain_config` · `update_domain_config` | +| `client.messaging_v1_domain_config_messaging_service_api` | `MessagingV1DomainConfigMessagingServiceApi` / `AsyncMessagingV1DomainConfigMessagingServiceApi` | 1 | `fetch_domain_config_messaging_service` | +| `client.messaging_v1_domain_validate_dns` | `MessagingV1DomainValidateDns` / `AsyncMessagingV1DomainValidateDns` | 1 | `fetch_domain_dns_validation` | +| `client.messaging_v1_external_campaign_api` | `MessagingV1ExternalCampaignApi` / `AsyncMessagingV1ExternalCampaignApi` | 1 | `create_external_campaign` | +| `client.messaging_v1_linkshortening_messaging_service_api` | `MessagingV1LinkshorteningMessagingServiceApi` / `AsyncMessagingV1LinkshorteningMessagingServiceApi` | 2 | `create_linkshortening_messaging_service` · `delete_linkshortening_messaging_service` | +| `client.messaging_v1_linkshortening_messaging_service_domain_association_api` | `MessagingV1LinkshorteningMessagingServiceDomainAssociationApi` / `AsyncMessagingV1LinkshorteningMessagingServiceDomainAssociationApi` | 1 | `fetch_linkshortening_messaging_service_domain_association` | +| `client.messaging_v1_phone_number` | `MessagingV1PhoneNumber` / `AsyncMessagingV1PhoneNumber` | 4 | `create_phone_number` · `delete_phone_number` · `fetch_phone_number` · `list_phone_number` | +| `client.messaging_v1_request_managed_cert_api` | `MessagingV1RequestManagedCertApi` / `AsyncMessagingV1RequestManagedCertApi` | 1 | `update_request_managed_cert` | +| `client.messaging_v1_service_api` | `MessagingV1ServiceApi` / `AsyncMessagingV1ServiceApi` | 5 | `create_service` · `delete_service` · `fetch_service` · `list_service` · `update_service` | +| `client.messaging_v1_short_code` | `MessagingV1ShortCode` / `AsyncMessagingV1ShortCode` | 4 | `create_short_code` · `delete_short_code` · `fetch_short_code2` · `list_short_code2` | +| `client.messaging_v1_tollfree_verification_api` | `MessagingV1TollfreeVerificationApi` / `AsyncMessagingV1TollfreeVerificationApi` | 5 | `create_tollfree_verification` · `delete_tollfree_verification` · `fetch_tollfree_verification` · `list_tollfree_verification` · `update_tollfree_verification` | +| `client.messaging_v1_us_app_to_person` | `MessagingV1UsAppToPerson` / `AsyncMessagingV1UsAppToPerson` | 5 | `create_us_app_to_person` · `delete_us_app_to_person` · `fetch_us_app_to_person` · `list_us_app_to_person` · `update_us_app_to_person` | +| `client.messaging_v1_us_app_to_person_usecase` | `MessagingV1UsAppToPersonUsecase` / `AsyncMessagingV1UsAppToPersonUsecase` | 1 | `fetch_us_app_to_person_usecase` | +| `client.messaging_v1_usecase_api` | `MessagingV1UsecaseApi` / `AsyncMessagingV1UsecaseApi` | 1 | `fetch_usecase` | +| `client.messaging_v2_channels_sender` | `MessagingV2ChannelsSender` / `AsyncMessagingV2ChannelsSender` | 5 | `create_channels_sender` · `delete_channels_sender` · `fetch_channels_sender` · `list_channels_sender` · `update_channels_sender` | +| `client.messaging_v2_domain_certs` | `MessagingV2DomainCerts` / `AsyncMessagingV2DomainCerts` | 1 | `fetch_domain_cert_v42` | +| `client.messaging_v2_typing_indicator` | `MessagingV2TypingIndicator` / `AsyncMessagingV2TypingIndicator` | 1 | `create_typing_indicator` | +| `client.messaging_v3_typing_indicator` | `MessagingV3TypingIndicator` / `AsyncMessagingV3TypingIndicator` | 1 | `create_v3_typing_indicator` | +| `client.numbers_v1_bulk_eligibility_api` | `NumbersV1BulkEligibilityApi` / `AsyncNumbersV1BulkEligibilityApi` | 2 | `create_bulk_eligibility` · `fetch_bulk_eligibility` | +| `client.numbers_v1_eligibility_api` | `NumbersV1EligibilityApi` / `AsyncNumbersV1EligibilityApi` | 1 | `create_eligibility` | +| `client.numbers_v1_porting_port_in_api` | `NumbersV1PortingPortInApi` / `AsyncNumbersV1PortingPortInApi` | 4 | `create_porting_port_in` · `delete_porting_port_in` · `fetch_porting_port_in` · `list_port_in_requests` | +| `client.numbers_v1_porting_port_in_phone_number_api` | `NumbersV1PortingPortInPhoneNumberApi` / `AsyncNumbersV1PortingPortInPhoneNumberApi` | 2 | `delete_porting_port_in_phone_number` · `fetch_porting_port_in_phone_number` | +| `client.numbers_v1_porting_portability_api` | `NumbersV1PortingPortabilityApi` / `AsyncNumbersV1PortingPortabilityApi` | 1 | `fetch_porting_portability` | +| `client.numbers_v1_porting_webhook_configuration_api` | `NumbersV1PortingWebhookConfigurationApi` / `AsyncNumbersV1PortingWebhookConfigurationApi` | 1 | `create_porting_webhook_configuration` | +| `client.numbers_v1_porting_webhook_configuration_delete_api` | `NumbersV1PortingWebhookConfigurationDeleteApi` / `AsyncNumbersV1PortingWebhookConfigurationDeleteApi` | 1 | `delete_porting_webhook_configuration_delete` | +| `client.numbers_v1_porting_webhook_configuration_fetch_api` | `NumbersV1PortingWebhookConfigurationFetchApi` / `AsyncNumbersV1PortingWebhookConfigurationFetchApi` | 1 | `fetch_porting_webhook_configuration_fetch` | +| `client.numbers_v1_sender_id_registration` | `NumbersV1SenderIdRegistration` / `AsyncNumbersV1SenderIdRegistration` | 1 | `create_sender_id_registration` | +| `client.numbers_v1_sender_id_registration_embedded_session` | `NumbersV1SenderIdRegistrationEmbeddedSession` / `AsyncNumbersV1SenderIdRegistrationEmbeddedSession` | 1 | `create_sender_id_registration_embedded_session` | +| `client.numbers_v1_signing_request_configuration_api` | `NumbersV1SigningRequestConfigurationApi` / `AsyncNumbersV1SigningRequestConfigurationApi` | 2 | `create_signing_request_configuration` · `list_signing_request_configuration` | +| `client.numbers_v2_authorization_document_api` | `NumbersV2AuthorizationDocumentApi` / `AsyncNumbersV2AuthorizationDocumentApi` | 4 | `create_authorization_document` · `delete_authorization_document` · `fetch_authorization_document` · `list_authorization_document` | +| `client.numbers_v2_bulk_hosted_number_order_api` | `NumbersV2BulkHostedNumberOrderApi` / `AsyncNumbersV2BulkHostedNumberOrderApi` | 2 | `create_bulk_hosted_number_order` · `fetch_bulk_hosted_number_order` | +| `client.numbers_v2_bundle` | `NumbersV2Bundle` / `AsyncNumbersV2Bundle` | 5 | `create_bundle` · `delete_bundle` · `fetch_bundle` · `list_bundle` · `update_bundle` | +| `client.numbers_v2_bundle_clone_api` | `NumbersV2BundleCloneApi` / `AsyncNumbersV2BundleCloneApi` | 1 | `create_bundle_clone` | +| `client.numbers_v2_bundle_copy` | `NumbersV2BundleCopy` / `AsyncNumbersV2BundleCopy` | 2 | `create_bundle_copy` · `list_bundle_copy` | +| `client.numbers_v2_dependent_hosted_number_order` | `NumbersV2DependentHostedNumberOrder` / `AsyncNumbersV2DependentHostedNumberOrder` | 1 | `list_dependent_hosted_number_order` | +| `client.numbers_v2_end_user` | `NumbersV2EndUser` / `AsyncNumbersV2EndUser` | 5 | `create_end_user` · `delete_end_user` · `fetch_end_user` · `list_end_user` · `update_end_user` | +| `client.numbers_v2_end_user_type` | `NumbersV2EndUserType` / `AsyncNumbersV2EndUserType` | 2 | `fetch_end_user_type` · `list_end_user_type` | +| `client.numbers_v2_evaluation` | `NumbersV2Evaluation` / `AsyncNumbersV2Evaluation` | 3 | `create_evaluation` · `fetch_evaluation` · `list_evaluation` | +| `client.numbers_v2_hosted_number_order_api` | `NumbersV2HostedNumberOrderApi` / `AsyncNumbersV2HostedNumberOrderApi` | 5 | `create_hosted_number_order` · `delete_hosted_number_order` · `fetch_hosted_number_order` · `list_hosted_number_order` · `update_hosted_number_order` | +| `client.numbers_v2_item_assignment` | `NumbersV2ItemAssignment` / `AsyncNumbersV2ItemAssignment` | 4 | `create_item_assignment` · `delete_item_assignment` · `fetch_item_assignment` · `list_item_assignment` | +| `client.numbers_v2_regulation` | `NumbersV2Regulation` / `AsyncNumbersV2Regulation` | 2 | `fetch_regulation` · `list_regulation` | +| `client.numbers_v2_replace_items` | `NumbersV2ReplaceItems` / `AsyncNumbersV2ReplaceItems` | 1 | `create_replace_items` | +| `client.numbers_v2_supporting_document` | `NumbersV2SupportingDocument` / `AsyncNumbersV2SupportingDocument` | 5 | `create_supporting_document` · `delete_supporting_document` · `fetch_supporting_document` · `list_supporting_document` · `update_supporting_document` | +| `client.numbers_v2_supporting_document_type` | `NumbersV2SupportingDocumentType` / `AsyncNumbersV2SupportingDocumentType` | 2 | `fetch_supporting_document_type` · `list_supporting_document_type` | +| `client.numbers_v3_hosted_numbers_hosted_number_order_api` | `NumbersV3HostedNumbersHostedNumberOrderApi` / `AsyncNumbersV3HostedNumbersHostedNumberOrderApi` | 1 | `create_hosted_numbers_hosted_number_order` | +| `client.proxy_v1_interaction` | `ProxyV1Interaction` / `AsyncProxyV1Interaction` | 3 | `delete_interaction` · `fetch_interaction` · `list_interaction` | +| `client.proxy_v1_message_interaction` | `ProxyV1MessageInteraction` / `AsyncProxyV1MessageInteraction` | 3 | `create_message_interaction` · `fetch_message_interaction` · `list_message_interaction` | +| `client.proxy_v1_participant` | `ProxyV1Participant` / `AsyncProxyV1Participant` | 4 | `create_participant2` · `delete_participant2` · `fetch_participant3` · `list_participant2` | +| `client.proxy_v1_phone_number` | `ProxyV1PhoneNumber` / `AsyncProxyV1PhoneNumber` | 5 | `create_phone_number2` · `delete_phone_number2` · `fetch_phone_number4` · `list_phone_number2` · `update_phone_number` | +| `client.proxy_v1_service_api` | `ProxyV1ServiceApi` / `AsyncProxyV1ServiceApi` | 5 | `create_service4` · `delete_service4` · `fetch_service4` · `list_service4` · `update_service3` | +| `client.proxy_v1_session` | `ProxyV1Session` / `AsyncProxyV1Session` | 5 | `create_session` · `delete_session` · `fetch_session` · `list_session` · `update_session` | +| `client.studio_v1_engagement` | `StudioV1Engagement` / `AsyncStudioV1Engagement` | 4 | `create_engagement` · `delete_engagement` · `fetch_engagement` · `list_engagement` | +| `client.studio_v1_engagement_context` | `StudioV1EngagementContext` / `AsyncStudioV1EngagementContext` | 1 | `fetch_engagement_context` | +| `client.studio_v1_execution` | `StudioV1Execution` / `AsyncStudioV1Execution` | 5 | `create_execution` · `delete_execution` · `fetch_execution` · `list_execution` · `update_execution` | +| `client.studio_v1_execution_context` | `StudioV1ExecutionContext` / `AsyncStudioV1ExecutionContext` | 1 | `fetch_execution_context` | +| `client.studio_v1_execution_step` | `StudioV1ExecutionStep` / `AsyncStudioV1ExecutionStep` | 2 | `fetch_execution_step` · `list_execution_step` | +| `client.studio_v1_execution_step_context` | `StudioV1ExecutionStepContext` / `AsyncStudioV1ExecutionStepContext` | 1 | `fetch_execution_step_context` | +| `client.studio_v1_flow_api` | `StudioV1FlowApi` / `AsyncStudioV1FlowApi` | 3 | `delete_flow` · `fetch_flow` · `list_flow` | +| `client.studio_v1_step` | `StudioV1Step` / `AsyncStudioV1Step` | 2 | `fetch_step` · `list_step` | +| `client.studio_v1_step_context` | `StudioV1StepContext` / `AsyncStudioV1StepContext` | 1 | `fetch_step_context` | +| `client.studio_v2_execution` | `StudioV2Execution` / `AsyncStudioV2Execution` | 5 | `create_execution2` · `delete_execution2` · `fetch_execution2` · `list_execution2` · `update_execution2` | +| `client.studio_v2_execution_context` | `StudioV2ExecutionContext` / `AsyncStudioV2ExecutionContext` | 1 | `fetch_execution_context2` | +| `client.studio_v2_execution_step` | `StudioV2ExecutionStep` / `AsyncStudioV2ExecutionStep` | 2 | `fetch_execution_step2` · `list_execution_step2` | +| `client.studio_v2_execution_step_context` | `StudioV2ExecutionStepContext` / `AsyncStudioV2ExecutionStepContext` | 1 | `fetch_execution_step_context2` | +| `client.studio_v2_flow_api` | `StudioV2FlowApi` / `AsyncStudioV2FlowApi` | 5 | `create_flow` · `delete_flow2` · `fetch_flow2` · `list_flow2` · `update_flow` | +| `client.studio_v2_flow_revision` | `StudioV2FlowRevision` / `AsyncStudioV2FlowRevision` | 2 | `fetch_flow_revision` · `list_flow_revision` | +| `client.studio_v2_flow_test_user_api` | `StudioV2FlowTestUserApi` / `AsyncStudioV2FlowTestUserApi` | 2 | `fetch_test_user` · `update_test_user` | +| `client.studio_v2_flow_validate_api` | `StudioV2FlowValidateApi` / `AsyncStudioV2FlowValidateApi` | 1 | `update_flow_validate` | +| `client.sync_v1_document` | `SyncV1Document` / `AsyncSyncV1Document` | 5 | `create_document` · `delete_document` · `fetch_document` · `list_document` · `update_document` | +| `client.sync_v1_document_permission` | `SyncV1DocumentPermission` / `AsyncSyncV1DocumentPermission` | 4 | `delete_document_permission` · `fetch_document_permission` · `list_document_permission` · `update_document_permission` | +| `client.sync_v1_service_api` | `SyncV1ServiceApi` / `AsyncSyncV1ServiceApi` | 5 | `create_service5` · `delete_service5` · `fetch_service5` · `list_service5` · `update_service4` | +| `client.sync_v1_stream_message` | `SyncV1StreamMessage` / `AsyncSyncV1StreamMessage` | 1 | `create_stream_message` | +| `client.sync_v1_sync_list` | `SyncV1SyncList` / `AsyncSyncV1SyncList` | 5 | `create_sync_list` · `delete_sync_list` · `fetch_sync_list` · `list_sync_list` · `update_sync_list` | +| `client.sync_v1_sync_list_item` | `SyncV1SyncListItem` / `AsyncSyncV1SyncListItem` | 5 | `create_sync_list_item` · `delete_sync_list_item` · `fetch_sync_list_item` · `list_sync_list_item` · `update_sync_list_item` | +| `client.sync_v1_sync_list_permission` | `SyncV1SyncListPermission` / `AsyncSyncV1SyncListPermission` | 4 | `delete_sync_list_permission` · `fetch_sync_list_permission` · `list_sync_list_permission` · `update_sync_list_permission` | +| `client.sync_v1_sync_map` | `SyncV1SyncMap` / `AsyncSyncV1SyncMap` | 5 | `create_sync_map` · `delete_sync_map` · `fetch_sync_map` · `list_sync_map` · `update_sync_map` | +| `client.sync_v1_sync_map_item` | `SyncV1SyncMapItem` / `AsyncSyncV1SyncMapItem` | 5 | `create_sync_map_item` · `delete_sync_map_item` · `fetch_sync_map_item` · `list_sync_map_item` · `update_sync_map_item` | +| `client.sync_v1_sync_map_permission` | `SyncV1SyncMapPermission` / `AsyncSyncV1SyncMapPermission` | 4 | `delete_sync_map_permission` · `fetch_sync_map_permission` · `list_sync_map_permission` · `update_sync_map_permission` | +| `client.sync_v1_sync_stream` | `SyncV1SyncStream` / `AsyncSyncV1SyncStream` | 5 | `create_sync_stream` · `delete_sync_stream` · `fetch_sync_stream` · `list_sync_stream` · `update_sync_stream` | +| `client.taskrouter_v1_activity` | `TaskrouterV1Activity` / `AsyncTaskrouterV1Activity` | 5 | `create_activity` · `delete_activity` · `fetch_activity` · `list_activity` · `update_activity` | +| `client.taskrouter_v1_event` | `TaskrouterV1Event` / `AsyncTaskrouterV1Event` | 2 | `fetch_event` · `list_event` | +| `client.taskrouter_v1_task` | `TaskrouterV1Task` / `AsyncTaskrouterV1Task` | 5 | `create_task` · `delete_task` · `fetch_task` · `list_task` · `update_task` | +| `client.taskrouter_v1_task_channel` | `TaskrouterV1TaskChannel` / `AsyncTaskrouterV1TaskChannel` | 5 | `create_task_channel` · `delete_task_channel` · `fetch_task_channel` · `list_task_channel` · `update_task_channel` | +| `client.taskrouter_v1_task_queue` | `TaskrouterV1TaskQueue` / `AsyncTaskrouterV1TaskQueue` | 5 | `create_task_queue` · `delete_task_queue` · `fetch_task_queue` · `list_task_queue` · `update_task_queue` | +| `client.taskrouter_v1_task_queue_bulk_real_time_statistics` | `TaskrouterV1TaskQueueBulkRealTimeStatistics` / `AsyncTaskrouterV1TaskQueueBulkRealTimeStatistics` | 1 | `create_task_queue_bulk_real_time_statistics` | +| `client.taskrouter_v1_task_queue_cumulative_statistics` | `TaskrouterV1TaskQueueCumulativeStatistics` / `AsyncTaskrouterV1TaskQueueCumulativeStatistics` | 1 | `fetch_task_queue_cumulative_statistics` | +| `client.taskrouter_v1_task_queue_real_time_statistics` | `TaskrouterV1TaskQueueRealTimeStatistics` / `AsyncTaskrouterV1TaskQueueRealTimeStatistics` | 1 | `fetch_task_queue_real_time_statistics` | +| `client.taskrouter_v1_task_queue_statistics` | `TaskrouterV1TaskQueueStatistics` / `AsyncTaskrouterV1TaskQueueStatistics` | 1 | `fetch_task_queue_statistics` | +| `client.taskrouter_v1_task_queues_statistics` | `TaskrouterV1TaskQueuesStatistics` / `AsyncTaskrouterV1TaskQueuesStatistics` | 1 | `list_task_queues_statistics` | +| `client.taskrouter_v1_task_reservation` | `TaskrouterV1TaskReservation` / `AsyncTaskrouterV1TaskReservation` | 3 | `fetch_task_reservation` · `list_task_reservation` · `update_task_reservation` | +| `client.taskrouter_v1_worker` | `TaskrouterV1Worker` / `AsyncTaskrouterV1Worker` | 5 | `create_worker` · `delete_worker` · `fetch_worker` · `list_worker` · `update_worker` | +| `client.taskrouter_v1_worker_channel` | `TaskrouterV1WorkerChannel` / `AsyncTaskrouterV1WorkerChannel` | 3 | `fetch_worker_channel` · `list_worker_channel` · `update_worker_channel` | +| `client.taskrouter_v1_worker_reservation` | `TaskrouterV1WorkerReservation` / `AsyncTaskrouterV1WorkerReservation` | 3 | `fetch_worker_reservation` · `list_worker_reservation` · `update_worker_reservation` | +| `client.taskrouter_v1_worker_statistics` | `TaskrouterV1WorkerStatistics` / `AsyncTaskrouterV1WorkerStatistics` | 1 | `fetch_worker_instance_statistics` | +| `client.taskrouter_v1_workers_cumulative_statistics` | `TaskrouterV1WorkersCumulativeStatistics` / `AsyncTaskrouterV1WorkersCumulativeStatistics` | 1 | `fetch_workers_cumulative_statistics` | +| `client.taskrouter_v1_workers_real_time_statistics` | `TaskrouterV1WorkersRealTimeStatistics` / `AsyncTaskrouterV1WorkersRealTimeStatistics` | 1 | `fetch_workers_real_time_statistics` | +| `client.taskrouter_v1_workers_statistics` | `TaskrouterV1WorkersStatistics` / `AsyncTaskrouterV1WorkersStatistics` | 1 | `fetch_worker_statistics` | +| `client.taskrouter_v1_workflow` | `TaskrouterV1Workflow` / `AsyncTaskrouterV1Workflow` | 5 | `create_workflow` · `delete_workflow` · `fetch_workflow` · `list_workflow` · `update_workflow` | +| `client.taskrouter_v1_workflow_cumulative_statistics` | `TaskrouterV1WorkflowCumulativeStatistics` / `AsyncTaskrouterV1WorkflowCumulativeStatistics` | 1 | `fetch_workflow_cumulative_statistics` | +| `client.taskrouter_v1_workflow_real_time_statistics` | `TaskrouterV1WorkflowRealTimeStatistics` / `AsyncTaskrouterV1WorkflowRealTimeStatistics` | 1 | `fetch_workflow_real_time_statistics` | +| `client.taskrouter_v1_workflow_statistics` | `TaskrouterV1WorkflowStatistics` / `AsyncTaskrouterV1WorkflowStatistics` | 1 | `fetch_workflow_statistics` | +| `client.taskrouter_v1_workspace_api` | `TaskrouterV1WorkspaceApi` / `AsyncTaskrouterV1WorkspaceApi` | 5 | `create_workspace` · `delete_workspace` · `fetch_workspace` · `list_workspace` · `update_workspace` | +| `client.taskrouter_v1_workspace_cumulative_statistics` | `TaskrouterV1WorkspaceCumulativeStatistics` / `AsyncTaskrouterV1WorkspaceCumulativeStatistics` | 1 | `fetch_workspace_cumulative_statistics` | +| `client.taskrouter_v1_workspace_real_time_statistics` | `TaskrouterV1WorkspaceRealTimeStatistics` / `AsyncTaskrouterV1WorkspaceRealTimeStatistics` | 1 | `fetch_workspace_real_time_statistics` | +| `client.taskrouter_v1_workspace_statistics` | `TaskrouterV1WorkspaceStatistics` / `AsyncTaskrouterV1WorkspaceStatistics` | 1 | `fetch_workspace_statistics` | +| `client.trusthub_v1_compliance_inquiries` | `TrusthubV1ComplianceInquiries` / `AsyncTrusthubV1ComplianceInquiries` | 2 | `create_compliance_inquiry` · `update_compliance_inquiry` | +| `client.trusthub_v1_compliance_registration_inquiries` | `TrusthubV1ComplianceRegistrationInquiries` / `AsyncTrusthubV1ComplianceRegistrationInquiries` | 2 | `create_compliance_registration` · `update_compliance_registration` | +| `client.trusthub_v1_compliance_tollfree_inquiries` | `TrusthubV1ComplianceTollfreeInquiries` / `AsyncTrusthubV1ComplianceTollfreeInquiries` | 1 | `create_compliance_tollfree_inquiry` | +| `client.trusthub_v1_customer_profiles` | `TrusthubV1CustomerProfiles` / `AsyncTrusthubV1CustomerProfiles` | 5 | `create_customer_profile` · `delete_customer_profile` · `fetch_customer_profile` · `list_customer_profile` · `update_customer_profile` | +| `client.trusthub_v1_customer_profiles_channel_endpoint_assignment` | `TrusthubV1CustomerProfilesChannelEndpointAssignment` / `AsyncTrusthubV1CustomerProfilesChannelEndpointAssignment` | 4 | `create_customer_profile_channel_endpoint_assignment` · `delete_customer_profile_channel_endpoint_assignment` · `fetch_customer_profile_channel_endpoint_assignment` · `list_customer_profile_channel_endpoint_assignment` | +| `client.trusthub_v1_customer_profiles_entity_assignments` | `TrusthubV1CustomerProfilesEntityAssignments` / `AsyncTrusthubV1CustomerProfilesEntityAssignments` | 4 | `create_customer_profile_entity_assignment` · `delete_customer_profile_entity_assignment` · `fetch_customer_profile_entity_assignment` · `list_customer_profile_entity_assignment` | +| `client.trusthub_v1_customer_profiles_evaluations` | `TrusthubV1CustomerProfilesEvaluations` / `AsyncTrusthubV1CustomerProfilesEvaluations` | 3 | `create_customer_profile_evaluation` · `fetch_customer_profile_evaluation` · `list_customer_profile_evaluation` | +| `client.trusthub_v1_end_user_api` | `TrusthubV1EndUserApi` / `AsyncTrusthubV1EndUserApi` | 5 | `create_end_user2` · `delete_end_user2` · `fetch_end_user2` · `list_end_user2` · `update_end_user2` | +| `client.trusthub_v1_end_user_type` | `TrusthubV1EndUserType` / `AsyncTrusthubV1EndUserType` | 2 | `fetch_end_user_type2` · `list_end_user_type2` | +| `client.trusthub_v1_policies_api` | `TrusthubV1PoliciesApi` / `AsyncTrusthubV1PoliciesApi` | 2 | `fetch_policies` · `list_policies` | +| `client.trusthub_v1_supporting_document_api` | `TrusthubV1SupportingDocumentApi` / `AsyncTrusthubV1SupportingDocumentApi` | 5 | `create_supporting_document2` · `delete_supporting_document2` · `fetch_supporting_document2` · `list_supporting_document2` · `update_supporting_document2` | +| `client.trusthub_v1_supporting_document_type` | `TrusthubV1SupportingDocumentType` / `AsyncTrusthubV1SupportingDocumentType` | 2 | `fetch_supporting_document_type2` · `list_supporting_document_type2` | +| `client.trusthub_v1_trust_products` | `TrusthubV1TrustProducts` / `AsyncTrusthubV1TrustProducts` | 5 | `create_trust_product` · `delete_trust_product` · `fetch_trust_product` · `list_trust_product` · `update_trust_product` | +| `client.trusthub_v1_trust_products_channel_endpoint_assignment` | `TrusthubV1TrustProductsChannelEndpointAssignment` / `AsyncTrusthubV1TrustProductsChannelEndpointAssignment` | 4 | `create_trust_product_channel_endpoint_assignment` · `delete_trust_product_channel_endpoint_assignment` · `fetch_trust_product_channel_endpoint_assignment` · `list_trust_product_channel_endpoint_assignment` | +| `client.trusthub_v1_trust_products_entity_assignments` | `TrusthubV1TrustProductsEntityAssignments` / `AsyncTrusthubV1TrustProductsEntityAssignments` | 4 | `create_trust_product_entity_assignment` · `delete_trust_product_entity_assignment` · `fetch_trust_product_entity_assignment` · `list_trust_product_entity_assignment` | +| `client.trusthub_v1_trust_products_evaluations` | `TrusthubV1TrustProductsEvaluations` / `AsyncTrusthubV1TrustProductsEvaluations` | 3 | `create_trust_product_evaluation` · `fetch_trust_product_evaluation` · `list_trust_product_evaluation` | +| `client.twilio_insights` | `TwilioInsights` / `AsyncTwilioInsights` | 3 | `create_query_results` · `fetch_metadata` · `fetch_query_results` | +| `client.v2_short_code_applications` | `V2ShortCodeApplications` / `AsyncV2ShortCodeApplications` | 3 | `create_short_code_application` · `fetch_short_code_application` · `list_short_code_applications` | +| `client.verify_v2_access_token` | `VerifyV2AccessToken` / `AsyncVerifyV2AccessToken` | 2 | `create_access_token` · `fetch_access_token` | +| `client.verify_v2_bucket` | `VerifyV2Bucket` / `AsyncVerifyV2Bucket` | 5 | `create_bucket` · `delete_bucket` · `fetch_bucket` · `list_bucket` · `update_bucket` | +| `client.verify_v2_challenge` | `VerifyV2Challenge` / `AsyncVerifyV2Challenge` | 4 | `create_challenge` · `fetch_challenge` · `list_challenge` · `update_challenge` | +| `client.verify_v2_entity` | `VerifyV2Entity` / `AsyncVerifyV2Entity` | 4 | `create_entity` · `delete_entity` · `fetch_entity` · `list_entity` | +| `client.verify_v2_factor` | `VerifyV2Factor` / `AsyncVerifyV2Factor` | 4 | `delete_factor` · `fetch_factor` · `list_factor` · `update_factor` | +| `client.verify_v2_form_api` | `VerifyV2FormApi` / `AsyncVerifyV2FormApi` | 1 | `fetch_form` | +| `client.verify_v2_messaging_configuration` | `VerifyV2MessagingConfiguration` / `AsyncVerifyV2MessagingConfiguration` | 5 | `create_messaging_configuration` · `delete_messaging_configuration` · `fetch_messaging_configuration` · `list_messaging_configuration` · `update_messaging_configuration` | +| `client.verify_v2_new_challenge` | `VerifyV2NewChallenge` / `AsyncVerifyV2NewChallenge` | 1 | `create_challenge_passkeys` | +| `client.verify_v2_new_factor` | `VerifyV2NewFactor` / `AsyncVerifyV2NewFactor` | 2 | `create_new_factor` · `create_new_factor_passkey` | +| `client.verify_v2_notification` | `VerifyV2Notification` / `AsyncVerifyV2Notification` | 1 | `create_notification` | +| `client.verify_v2_rate_limit` | `VerifyV2RateLimit` / `AsyncVerifyV2RateLimit` | 5 | `create_rate_limit` · `delete_rate_limit` · `fetch_rate_limit` · `list_rate_limit` · `update_rate_limit` | +| `client.verify_v2_safelist_api` | `VerifyV2SafelistApi` / `AsyncVerifyV2SafelistApi` | 3 | `create_safelist` · `delete_safelist` · `fetch_safelist` | +| `client.verify_v2_service_api` | `VerifyV2ServiceApi` / `AsyncVerifyV2ServiceApi` | 5 | `create_service2` · `delete_service2` · `fetch_service2` · `list_service2` · `update_service2` | +| `client.verify_v2_template` | `VerifyV2Template` / `AsyncVerifyV2Template` | 1 | `list_verification_template` | +| `client.verify_v2_verification` | `VerifyV2Verification` / `AsyncVerifyV2Verification` | 3 | `create_verification` · `fetch_verification` · `update_verification` | +| `client.verify_v2_verification_attempt_api` | `VerifyV2VerificationAttemptApi` / `AsyncVerifyV2VerificationAttemptApi` | 2 | `fetch_verification_attempt` · `list_verification_attempt` | +| `client.verify_v2_verification_attempts_summary_api` | `VerifyV2VerificationAttemptsSummaryApi` / `AsyncVerifyV2VerificationAttemptsSummaryApi` | 1 | `fetch_verification_attempts_summary` | +| `client.verify_v2_verification_check` | `VerifyV2VerificationCheck` / `AsyncVerifyV2VerificationCheck` | 1 | `create_verification_check` | +| `client.verify_v2_webhook` | `VerifyV2Webhook` / `AsyncVerifyV2Webhook` | 5 | `create_webhook` · `delete_webhook` · `fetch_webhook` · `list_webhook` · `update_webhook` | +| `client.video_v1_anonymize` | `VideoV1Anonymize` / `AsyncVideoV1Anonymize` | 1 | `update_room_participant_anonymize` | +| `client.video_v1_composition_api` | `VideoV1CompositionApi` / `AsyncVideoV1CompositionApi` | 4 | `create_composition` · `delete_composition` · `fetch_composition` · `list_composition` | +| `client.video_v1_composition_hook_api` | `VideoV1CompositionHookApi` / `AsyncVideoV1CompositionHookApi` | 5 | `create_composition_hook` · `delete_composition_hook` · `fetch_composition_hook` · `list_composition_hook` · `update_composition_hook` | +| `client.video_v1_composition_settings_api` | `VideoV1CompositionSettingsApi` / `AsyncVideoV1CompositionSettingsApi` | 2 | `create_composition_settings` · `fetch_composition_settings` | +| `client.video_v1_participant` | `VideoV1Participant` / `AsyncVideoV1Participant` | 3 | `fetch_room_participant` · `list_room_participant` · `update_room_participant` | +| `client.video_v1_published_track` | `VideoV1PublishedTrack` / `AsyncVideoV1PublishedTrack` | 2 | `fetch_room_participant_published_track` · `list_room_participant_published_track` | +| `client.video_v1_recording_api` | `VideoV1RecordingApi` / `AsyncVideoV1RecordingApi` | 3 | `delete_recording2` · `fetch_recording2` · `list_recording2` | +| `client.video_v1_recording_rules` | `VideoV1RecordingRules` / `AsyncVideoV1RecordingRules` | 2 | `fetch_room_recording_rule` · `update_room_recording_rule` | +| `client.video_v1_recording_settings_api` | `VideoV1RecordingSettingsApi` / `AsyncVideoV1RecordingSettingsApi` | 2 | `create_recording_settings` · `fetch_recording_settings` | +| `client.video_v1_room_api` | `VideoV1RoomApi` / `AsyncVideoV1RoomApi` | 4 | `create_room` · `fetch_room` · `list_room` · `update_room` | +| `client.video_v1_room_recording` | `VideoV1RoomRecording` / `AsyncVideoV1RoomRecording` | 3 | `delete_room_recording` · `fetch_room_recording` · `list_room_recording` | +| `client.video_v1_subscribe_rules` | `VideoV1SubscribeRules` / `AsyncVideoV1SubscribeRules` | 2 | `fetch_room_participant_subscribe_rule` · `update_room_participant_subscribe_rule` | +| `client.video_v1_subscribed_track` | `VideoV1SubscribedTrack` / `AsyncVideoV1SubscribedTrack` | 2 | `fetch_room_participant_subscribed_track` · `list_room_participant_subscribed_track` | +| `client.video_v1_transcriptions` | `VideoV1Transcriptions` / `AsyncVideoV1Transcriptions` | 4 | `create_room_transcriptions` · `fetch_room_transcriptions` · `list_room_transcriptions` · `update_room_transcriptions` | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `twilio_sdk/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `twilio_sdk/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/twilio-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `twilio_sdk/models/accounts_calls_recordings_sid_json201041408_error.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `twilio_sdk/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import twilio_sdk, pathlib; print(pathlib.Path(twilio_sdk.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/twilio_sdk` on Windows, `.venv/lib/python3.*/site-packages/twilio_sdk` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The one scheme is `account_sid_auth_token=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **128 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`), so an unknown wire value passes through as a plain `str` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 128 operations that return `None`** — `delete_lookup_phone_number_overrides` · `delete_lookup_rate_limit` · `api20100401_add_on_result.delete_recording_add_on_result` · `api20100401_address.delete_address` · `api20100401_application.delete_application` · `api20100401_assigned_add_on.delete_incoming_phone_number_assigned_add_on` · `api20100401_auth_calls_credential_list_mapping.delete_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.delete_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.delete_sip_auth_registrations_credential_list_mapping` · `api20100401_call.delete_call` · `api20100401_call_recording.delete_call_recording` · `api20100401_conference_recording.delete_conference_recording` · `api20100401_connect_app.delete_connect_app` · `api20100401_credential.delete_sip_credential` · `api20100401_credential_list.delete_sip_credential_list` · `api20100401_credential_list_mapping.delete_sip_credential_list_mapping` · `api20100401_data.fetch_recording_add_on_result_payload_data` · `api20100401_domain.delete_sip_domain` · `api20100401_incoming_phone_number.delete_incoming_phone_number` · `api20100401_ip_access_control_list.delete_sip_ip_access_control_list` · `api20100401_ip_access_control_list_mapping.delete_sip_ip_access_control_list_mapping` · `api20100401_key.delete_key` · `api20100401_media_instance.delete_media` · `api20100401_message.delete_message` · `api20100401_outgoing_caller_id.delete_outgoing_caller_id` · `api20100401_participant.delete_participant` · `api20100401_payload.delete_recording_add_on_result_payload` · `api20100401_queue.delete_queue` · `api20100401_recording.delete_recording` · `api20100401_recording_transcription.delete_recording_transcription` · `api20100401_signing_key.delete_signing_key` · `api20100401_sip_ip_address.delete_sip_ip_address` · `api20100401_transcription.delete_transcription` · `api20100401_trigger.delete_usage_trigger` · `api20100401_user_defined_message_subscription.delete_user_defined_message_subscription` · `contentv1_content_api.delete_content` · `conversations_v1_address_configuration.delete_configuration_address` · `conversations_v1_binding.delete_service_binding` · `conversations_v1_conversation_api.delete_conversation` · `conversations_v1_conversation_api.delete_service_conversation` · `conversations_v1_credential_api.delete_credential` · `conversations_v1_message.delete_conversation_message` · `conversations_v1_message.delete_service_conversation_message` · `conversations_v1_participant.delete_conversation_participant` · `conversations_v1_participant.delete_service_conversation_participant` · `conversations_v1_role_api.delete_role` · `conversations_v1_role_api.delete_service_role` · `conversations_v1_service_api.delete_service3` · `conversations_v1_user_api.delete_service_user` · `conversations_v1_user_api.delete_user` · `conversations_v1_user_conversation.delete_service_user_conversation` · `conversations_v1_user_conversation.delete_user_conversation` · `conversations_v1_webhook.delete_conversation_scoped_webhook` · `conversations_v1_webhook.delete_service_conversation_scoped_webhook` · `flex_v1_channel_api.delete_channel` · `flex_v1_flex_flow_api.delete_flex_flow` · `flex_v1_insights_questionnaires_api.delete_insights_questionnaires` · `flex_v1_insights_questionnaires_category_api.delete_insights_questionnaires_category` · `flex_v1_insights_questionnaires_question_api.delete_insights_questionnaires_question` · `flex_v1_web_channel_api.delete_web_channel` · `messaging_v1_alpha_sender.delete_alpha_sender` · `messaging_v1_channel_sender.delete_channel_sender` · `messaging_v1_deactivations.fetch_deactivation` · `messaging_v1_destination_alpha_sender.delete_destination_alpha_sender` · `messaging_v1_domain_certs.delete_domain_cert_v4` · `messaging_v1_linkshortening_messaging_service_api.delete_linkshortening_messaging_service` · `messaging_v1_phone_number.delete_phone_number` · `messaging_v1_service_api.delete_service` · `messaging_v1_short_code.delete_short_code` · `messaging_v1_tollfree_verification_api.delete_tollfree_verification` · `messaging_v1_us_app_to_person.delete_us_app_to_person` · `messaging_v2_channels_sender.delete_channels_sender` · `numbers_v1_porting_port_in_api.delete_porting_port_in` · `numbers_v1_porting_port_in_phone_number_api.delete_porting_port_in_phone_number` · `numbers_v1_porting_webhook_configuration_delete_api.delete_porting_webhook_configuration_delete` · `numbers_v2_authorization_document_api.delete_authorization_document` · `numbers_v2_bundle.delete_bundle` · `numbers_v2_end_user.delete_end_user` · `numbers_v2_hosted_number_order_api.delete_hosted_number_order` · `numbers_v2_item_assignment.delete_item_assignment` · `numbers_v2_supporting_document.delete_supporting_document` · `proxy_v1_interaction.delete_interaction` · `proxy_v1_participant.delete_participant2` · `proxy_v1_phone_number.delete_phone_number2` · `proxy_v1_service_api.delete_service4` · `proxy_v1_session.delete_session` · `studio_v1_engagement.delete_engagement` · `studio_v1_execution.delete_execution` · `studio_v1_flow_api.delete_flow` · `studio_v2_execution.delete_execution2` · `studio_v2_flow_api.delete_flow2` · `sync_v1_document.delete_document` · `sync_v1_document_permission.delete_document_permission` · `sync_v1_service_api.delete_service5` · `sync_v1_sync_list.delete_sync_list` · `sync_v1_sync_list_item.delete_sync_list_item` · `sync_v1_sync_list_permission.delete_sync_list_permission` · `sync_v1_sync_map.delete_sync_map` · `sync_v1_sync_map_item.delete_sync_map_item` · `sync_v1_sync_map_permission.delete_sync_map_permission` · `sync_v1_sync_stream.delete_sync_stream` · `taskrouter_v1_activity.delete_activity` · `taskrouter_v1_task.delete_task` · `taskrouter_v1_task_channel.delete_task_channel` · `taskrouter_v1_task_queue.delete_task_queue` · `taskrouter_v1_worker.delete_worker` · `taskrouter_v1_workflow.delete_workflow` · `taskrouter_v1_workspace_api.delete_workspace` · `trusthub_v1_customer_profiles.delete_customer_profile` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.delete_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_entity_assignments.delete_customer_profile_entity_assignment` · `trusthub_v1_end_user_api.delete_end_user2` · `trusthub_v1_supporting_document_api.delete_supporting_document2` · `trusthub_v1_trust_products.delete_trust_product` · `trusthub_v1_trust_products_channel_endpoint_assignment.delete_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_entity_assignments.delete_trust_product_entity_assignment` · `verify_v2_bucket.delete_bucket` · `verify_v2_entity.delete_entity` · `verify_v2_factor.delete_factor` · `verify_v2_messaging_configuration.delete_messaging_configuration` · `verify_v2_rate_limit.delete_rate_limit` · `verify_v2_safelist_api.delete_safelist` · `verify_v2_service_api.delete_service2` · `verify_v2_webhook.delete_webhook` · `video_v1_composition_api.delete_composition` · `video_v1_composition_hook_api.delete_composition_hook` · `video_v1_recording_api.delete_recording2` · `video_v1_room_recording.delete_room_recording`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — there are **seven** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `AccountsCallsRecordingsSidJson201041408Error1` — 34 operations (`create_lookup_phone_number_overrides` · `delete_lookup_phone_number_overrides` · `delete_lookup_rate_limit` · `fetch_lookup_account_rate_limits` · `fetch_lookup_phone_number_overrides` · `fetch_lookup_rate_limit` · `update_lookup_phone_number_overrides` · `update_lookup_rate_limit` · `api20100401_call_recording.update_call_recording` · `conversations_v2_action_api.create_conversation_action` · `conversations_v2_action_api.fetch_conversation_action` · `conversations_v2_communication_api.create_communication_in_conversation` · `conversations_v2_communication_api.fetch_communication` · `conversations_v2_communication_api.list_communication_by_conversation` · `conversations_v2_configuration_api.create_configuration` · `conversations_v2_configuration_api.delete_configuration` · `conversations_v2_configuration_api.fetch_configuration2` · `conversations_v2_configuration_api.list_configuration` · `conversations_v2_configuration_api.update_configuration2` · `conversations_v2_conversation_api.create_conversation_with_config` · `conversations_v2_conversation_api.delete_conversation_async` · `conversations_v2_conversation_api.fetch_conversation2` · `conversations_v2_conversation_api.list_conversation_by_account` · `conversations_v2_conversation_api.patch_conversation_by_id` · `conversations_v2_conversation_api.update_conversation_by_id` · `conversations_v2_operation.fetch_operation_status` · `conversations_v2_participant_api.create_participant_in_conversation` · `conversations_v2_participant_api.fetch_participant2` · `conversations_v2_participant_api.list_participant_by_conversation` · `conversations_v2_participant_api.update_participant_in_conversation` · `messaging_v3_typing_indicator.create_v3_typing_indicator` · `numbers_v1_sender_id_registration.create_sender_id_registration` · `numbers_v1_sender_id_registration_embedded_session.create_sender_id_registration_embedded_session` · `verify_v2_verification.create_verification`); distinguishing members *none required* + 2. `V3InsightsDomainsConversationsQuery400Error1` — 2 operations (`twilio_insights.create_query_results` · `twilio_insights.fetch_query_results`); distinguishing members `code` · `message` · `http_status_code` + 3. `V3InsightsDomainsConversationsQuery429Error1` — 2 operations (`twilio_insights.create_query_results` · `twilio_insights.fetch_query_results`); distinguishing members `code` · `message` · `http_status_code` + 4. `V3InsightsDomainsConversationsQuery500Error1` — 2 operations (`twilio_insights.create_query_results` · `twilio_insights.fetch_query_results`); distinguishing members `code` · `message` · `http_status_code` + 5. `V3InsightsDomainsConversationsMetadata400Error1` — 1 operation (`twilio_insights.fetch_metadata`); distinguishing members `code` · `message` · `http_status_code` + 6. `V3InsightsDomainsConversationsMetadata429Error1` — 1 operation (`twilio_insights.fetch_metadata`); distinguishing members `code` · `message` · `http_status_code` + 7. `V3InsightsDomainsConversationsMetadata500Error1` — 1 operation (`twilio_insights.fetch_metadata`); distinguishing members `code` · `message` · `http_status_code` + 8. *(none)* — `create_bulk_lookup` · `update_challenge_passkeys` · `update_passkeys_factor` · `api20100401_account.create_account` · `api20100401_account.fetch_account` · `api20100401_account.list_account` · `api20100401_account.update_account` · `api20100401_add_on_result.delete_recording_add_on_result` · `api20100401_add_on_result.fetch_recording_add_on_result` · `api20100401_add_on_result.list_recording_add_on_result` · `api20100401_address.create_address` · `api20100401_address.delete_address` · `api20100401_address.fetch_address` · `api20100401_address.list_address` · `api20100401_address.update_address` · `api20100401_all_time.list_usage_record_all_time` · `api20100401_application.create_application` · `api20100401_application.delete_application` · `api20100401_application.fetch_application` · `api20100401_application.list_application` · `api20100401_application.update_application` · `api20100401_assigned_add_on.create_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on.delete_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on.fetch_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on.list_incoming_phone_number_assigned_add_on` · `api20100401_assigned_add_on_extension.fetch_incoming_phone_number_assigned_add_on_extension` · `api20100401_assigned_add_on_extension.list_incoming_phone_number_assigned_add_on_extension` · `api20100401_auth_calls_credential_list_mapping.create_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_credential_list_mapping.delete_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_credential_list_mapping.fetch_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_credential_list_mapping.list_sip_auth_calls_credential_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.create_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.delete_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.fetch_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_calls_ip_access_control_list_mapping.list_sip_auth_calls_ip_access_control_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.create_sip_auth_registrations_credential_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.delete_sip_auth_registrations_credential_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.fetch_sip_auth_registrations_credential_list_mapping` · `api20100401_auth_registrations_credential_list_mapping.list_sip_auth_registrations_credential_list_mapping` · `api20100401_authorized_connect_app.fetch_authorized_connect_app` · `api20100401_authorized_connect_app.list_authorized_connect_app` · `api20100401_available_phone_number_country.fetch_available_phone_number_country` · `api20100401_available_phone_number_country.list_available_phone_number_country` · `api20100401_balance.fetch_balance` · `api20100401_call.create_call` · `api20100401_call.delete_call` · `api20100401_call.fetch_call` · `api20100401_call.list_call` · `api20100401_call.update_call` · `api20100401_call_notification.fetch_call_notification` · `api20100401_call_notification.list_call_notification` · `api20100401_call_recording.create_call_recording` · `api20100401_call_recording.delete_call_recording` · `api20100401_call_recording.fetch_call_recording` · `api20100401_call_recording.list_call_recording` · `api20100401_call_transcription.create_realtime_transcription` · `api20100401_call_transcription.update_realtime_transcription` · `api20100401_conference.fetch_conference` · `api20100401_conference.list_conference` · `api20100401_conference.update_conference` · `api20100401_conference_recording.delete_conference_recording` · `api20100401_conference_recording.fetch_conference_recording` · `api20100401_conference_recording.list_conference_recording` · `api20100401_conference_recording.update_conference_recording` · `api20100401_connect_app.delete_connect_app` · `api20100401_connect_app.fetch_connect_app` · `api20100401_connect_app.list_connect_app` · `api20100401_connect_app.update_connect_app` · `api20100401_credential.create_sip_credential` · `api20100401_credential.delete_sip_credential` · `api20100401_credential.fetch_sip_credential` · `api20100401_credential.list_sip_credential` · `api20100401_credential.update_sip_credential` · `api20100401_credential_list.create_sip_credential_list` · `api20100401_credential_list.delete_sip_credential_list` · `api20100401_credential_list.fetch_sip_credential_list` · `api20100401_credential_list.list_sip_credential_list` · `api20100401_credential_list.update_sip_credential_list` · `api20100401_credential_list_mapping.create_sip_credential_list_mapping` · `api20100401_credential_list_mapping.delete_sip_credential_list_mapping` · `api20100401_credential_list_mapping.fetch_sip_credential_list_mapping` · `api20100401_credential_list_mapping.list_sip_credential_list_mapping` · `api20100401_daily.list_usage_record_daily` · `api20100401_data.fetch_recording_add_on_result_payload_data` · `api20100401_dependent_phone_number.list_dependent_phone_number` · `api20100401_domain.create_sip_domain` · `api20100401_domain.delete_sip_domain` · `api20100401_domain.fetch_sip_domain` · `api20100401_domain.list_sip_domain` · `api20100401_domain.update_sip_domain` · `api20100401_event.list_call_event` · `api20100401_feedback.create_message_feedback` · `api20100401_incoming_phone_number.create_incoming_phone_number` · `api20100401_incoming_phone_number.delete_incoming_phone_number` · `api20100401_incoming_phone_number.fetch_incoming_phone_number` · `api20100401_incoming_phone_number.list_incoming_phone_number` · `api20100401_incoming_phone_number.update_incoming_phone_number` · `api20100401_incoming_phone_number_local.create_incoming_phone_number_local` · `api20100401_incoming_phone_number_local.list_incoming_phone_number_local` · `api20100401_incoming_phone_number_mobile.create_incoming_phone_number_mobile` · `api20100401_incoming_phone_number_mobile.list_incoming_phone_number_mobile` · `api20100401_incoming_phone_number_toll_free.create_incoming_phone_number_toll_free` · `api20100401_incoming_phone_number_toll_free.list_incoming_phone_number_toll_free` · `api20100401_ip_access_control_list.create_sip_ip_access_control_list` · `api20100401_ip_access_control_list.delete_sip_ip_access_control_list` · `api20100401_ip_access_control_list.fetch_sip_ip_access_control_list` · `api20100401_ip_access_control_list.list_sip_ip_access_control_list` · `api20100401_ip_access_control_list.update_sip_ip_access_control_list` · `api20100401_ip_access_control_list_mapping.create_sip_ip_access_control_list_mapping` · `api20100401_ip_access_control_list_mapping.delete_sip_ip_access_control_list_mapping` · `api20100401_ip_access_control_list_mapping.fetch_sip_ip_access_control_list_mapping` · `api20100401_ip_access_control_list_mapping.list_sip_ip_access_control_list_mapping` · `api20100401_key.delete_key` · `api20100401_key.fetch_key` · `api20100401_key.list_key` · `api20100401_key.update_key` · `api20100401_last_month.list_usage_record_last_month` · `api20100401_local.list_available_phone_number_local` · `api20100401_machine_to_machine.list_available_phone_number_machine_to_machine` · `api20100401_media.list_media` · `api20100401_media_instance.delete_media` · `api20100401_media_instance.fetch_media` · `api20100401_member.fetch_member` · `api20100401_member.list_member` · `api20100401_member.update_member` · `api20100401_message.create_message` · `api20100401_message.delete_message` · `api20100401_message.fetch_message` · `api20100401_message.list_message` · `api20100401_message.update_message` · `api20100401_mobile.list_available_phone_number_mobile` · `api20100401_monthly.list_usage_record_monthly` · `api20100401_national.list_available_phone_number_national` · `api20100401_new_key.create_new_key` · `api20100401_new_signing_key.create_new_signing_key` · `api20100401_notification.fetch_notification` · `api20100401_notification.list_notification` · `api20100401_outgoing_caller_id.delete_outgoing_caller_id` · `api20100401_outgoing_caller_id.fetch_outgoing_caller_id` · `api20100401_outgoing_caller_id.list_outgoing_caller_id` · `api20100401_outgoing_caller_id.update_outgoing_caller_id` · `api20100401_participant.create_participant` · `api20100401_participant.delete_participant` · `api20100401_participant.fetch_participant` · `api20100401_participant.list_participant` · `api20100401_participant.update_participant` · `api20100401_payload.delete_recording_add_on_result_payload` · `api20100401_payload.fetch_recording_add_on_result_payload` · `api20100401_payload.list_recording_add_on_result_payload` · `api20100401_payment.create_payments` · `api20100401_payment.update_payments` · `api20100401_queue.create_queue` · `api20100401_queue.delete_queue` · `api20100401_queue.fetch_queue` · `api20100401_queue.list_queue` · `api20100401_queue.update_queue` · `api20100401_record.list_usage_record` · `api20100401_recording.delete_recording` · `api20100401_recording.fetch_recording` · `api20100401_recording.list_recording` · `api20100401_recording_transcription.delete_recording_transcription` · `api20100401_recording_transcription.fetch_recording_transcription` · `api20100401_recording_transcription.list_recording_transcription` · `api20100401_shared_cost.list_available_phone_number_shared_cost` · `api20100401_short_code.fetch_short_code` · `api20100401_short_code.list_short_code` · `api20100401_short_code.update_short_code` · `api20100401_signing_key.delete_signing_key` · `api20100401_signing_key.fetch_signing_key` · `api20100401_signing_key.list_signing_key` · `api20100401_signing_key.update_signing_key` · `api20100401_sip_ip_address.create_sip_ip_address` · `api20100401_sip_ip_address.delete_sip_ip_address` · `api20100401_sip_ip_address.fetch_sip_ip_address` · `api20100401_sip_ip_address.list_sip_ip_address` · `api20100401_sip_ip_address.update_sip_ip_address` · `api20100401_siprec.create_siprec` · `api20100401_siprec.update_siprec` · `api20100401_stream.create_stream` · `api20100401_stream.update_stream` · `api20100401_this_month.list_usage_record_this_month` · `api20100401_today.list_usage_record_today` · `api20100401_token.create_token` · `api20100401_toll_free.list_available_phone_number_toll_free` · `api20100401_transcription.delete_transcription` · `api20100401_transcription.fetch_transcription` · `api20100401_transcription.list_transcription` · `api20100401_trigger.create_usage_trigger` · `api20100401_trigger.delete_usage_trigger` · `api20100401_trigger.fetch_usage_trigger` · `api20100401_trigger.list_usage_trigger` · `api20100401_trigger.update_usage_trigger` · `api20100401_user_defined_message.create_user_defined_message` · `api20100401_user_defined_message_subscription.create_user_defined_message_subscription` · `api20100401_user_defined_message_subscription.delete_user_defined_message_subscription` · `api20100401_validation_request.create_validation_request` · `api20100401_voip.list_available_phone_number_voip` · `api20100401_yearly.list_usage_record_yearly` · `api20100401_yesterday.list_usage_record_yesterday` · `content_v2_content.list_content2` · `content_v2_content_and_approvals.list_content_and_approvals2` · `contentv1_approval_create.create_approval_create` · `contentv1_approval_fetch.fetch_approval_fetch` · `contentv1_content_api.create_content` · `contentv1_content_api.delete_content` · `contentv1_content_api.fetch_content` · `contentv1_content_api.list_content` · `contentv1_content_api.update_content` · `contentv1_content_and_approvals_api.list_content_and_approvals` · `contentv1_legacy_content_api.list_legacy_content` · `conversations_v1_address_configuration.create_configuration_address` · `conversations_v1_address_configuration.delete_configuration_address` · `conversations_v1_address_configuration.fetch_configuration_address` · `conversations_v1_address_configuration.list_configuration_address` · `conversations_v1_address_configuration.update_configuration_address` · `conversations_v1_binding.delete_service_binding` · `conversations_v1_binding.fetch_service_binding` · `conversations_v1_binding.list_service_binding` · `conversations_v1_configuration_api.fetch_configuration` · `conversations_v1_configuration_api.fetch_service_configuration` · `conversations_v1_configuration_api.update_configuration` · `conversations_v1_configuration_api.update_service_configuration` · `conversations_v1_conversation_api.create_conversation` · `conversations_v1_conversation_api.create_service_conversation` · `conversations_v1_conversation_api.delete_conversation` · `conversations_v1_conversation_api.delete_service_conversation` · `conversations_v1_conversation_api.fetch_conversation` · `conversations_v1_conversation_api.fetch_service_conversation` · `conversations_v1_conversation_api.list_conversation` · `conversations_v1_conversation_api.list_service_conversation` · `conversations_v1_conversation_api.update_conversation` · `conversations_v1_conversation_api.update_service_conversation` · `conversations_v1_conversation_with_participants_api.create_conversation_with_participants` · `conversations_v1_conversation_with_participants_api.create_service_conversation_with_participants` · `conversations_v1_credential_api.create_credential` · `conversations_v1_credential_api.delete_credential` · `conversations_v1_credential_api.fetch_credential` · `conversations_v1_credential_api.list_credential` · `conversations_v1_credential_api.update_credential` · `conversations_v1_delivery_receipt.fetch_conversation_message_receipt` · `conversations_v1_delivery_receipt.fetch_service_conversation_message_receipt` · `conversations_v1_delivery_receipt.list_conversation_message_receipt` · `conversations_v1_delivery_receipt.list_service_conversation_message_receipt` · `conversations_v1_message.create_conversation_message` · `conversations_v1_message.create_service_conversation_message` · `conversations_v1_message.delete_conversation_message` · `conversations_v1_message.delete_service_conversation_message` · `conversations_v1_message.fetch_conversation_message` · `conversations_v1_message.fetch_service_conversation_message` · `conversations_v1_message.list_conversation_message` · `conversations_v1_message.list_service_conversation_message` · `conversations_v1_message.update_conversation_message` · `conversations_v1_message.update_service_conversation_message` · `conversations_v1_notification.fetch_service_notification` · `conversations_v1_notification.update_service_notification` · `conversations_v1_participant.create_conversation_participant` · `conversations_v1_participant.create_service_conversation_participant` · `conversations_v1_participant.delete_conversation_participant` · `conversations_v1_participant.delete_service_conversation_participant` · `conversations_v1_participant.fetch_conversation_participant` · `conversations_v1_participant.fetch_service_conversation_participant` · `conversations_v1_participant.list_conversation_participant` · `conversations_v1_participant.list_service_conversation_participant` · `conversations_v1_participant.update_conversation_participant` · `conversations_v1_participant.update_service_conversation_participant` · `conversations_v1_participant_conversation_api.list_participant_conversation` · `conversations_v1_participant_conversation_api.list_service_participant_conversation` · `conversations_v1_role_api.create_role` · `conversations_v1_role_api.create_service_role` · `conversations_v1_role_api.delete_role` · `conversations_v1_role_api.delete_service_role` · `conversations_v1_role_api.fetch_role` · `conversations_v1_role_api.fetch_service_role` · `conversations_v1_role_api.list_role` · `conversations_v1_role_api.list_service_role` · `conversations_v1_role_api.update_role` · `conversations_v1_role_api.update_service_role` · `conversations_v1_service_api.create_service3` · `conversations_v1_service_api.delete_service3` · `conversations_v1_service_api.fetch_service3` · `conversations_v1_service_api.list_service3` · `conversations_v1_user_api.create_service_user` · `conversations_v1_user_api.create_user` · `conversations_v1_user_api.delete_service_user` · `conversations_v1_user_api.delete_user` · `conversations_v1_user_api.fetch_service_user` · `conversations_v1_user_api.fetch_user` · `conversations_v1_user_api.list_service_user` · `conversations_v1_user_api.list_user` · `conversations_v1_user_api.update_service_user` · `conversations_v1_user_api.update_user` · `conversations_v1_user_conversation.delete_service_user_conversation` · `conversations_v1_user_conversation.delete_user_conversation` · `conversations_v1_user_conversation.fetch_service_user_conversation` · `conversations_v1_user_conversation.fetch_user_conversation` · `conversations_v1_user_conversation.list_service_user_conversation` · `conversations_v1_user_conversation.list_user_conversation` · `conversations_v1_user_conversation.update_service_user_conversation` · `conversations_v1_user_conversation.update_user_conversation` · `conversations_v1_webhook.create_conversation_scoped_webhook` · `conversations_v1_webhook.create_service_conversation_scoped_webhook` · `conversations_v1_webhook.delete_conversation_scoped_webhook` · `conversations_v1_webhook.delete_service_conversation_scoped_webhook` · `conversations_v1_webhook.fetch_configuration_webhook` · `conversations_v1_webhook.fetch_conversation_scoped_webhook` · `conversations_v1_webhook.fetch_service_conversation_scoped_webhook` · `conversations_v1_webhook.fetch_service_webhook_configuration` · `conversations_v1_webhook.list_conversation_scoped_webhook` · `conversations_v1_webhook.list_service_conversation_scoped_webhook` · `conversations_v1_webhook.update_configuration_webhook` · `conversations_v1_webhook.update_conversation_scoped_webhook` · `conversations_v1_webhook.update_service_conversation_scoped_webhook` · `conversations_v1_webhook.update_service_webhook_configuration` · `flex_v1_assessments.create_insights_assessments` · `flex_v1_assessments.list_insights_assessments` · `flex_v1_assessments.update_insights_assessments` · `flex_v1_channel_api.create_channel` · `flex_v1_channel_api.delete_channel` · `flex_v1_channel_api.fetch_channel` · `flex_v1_channel_api.list_channel` · `flex_v1_configuration_api.fetch_configuration3` · `flex_v1_configuration_api.update_configuration3` · `flex_v1_configured_plugin.fetch_configured_plugin` · `flex_v1_configured_plugin.list_configured_plugin` · `flex_v1_flex_flow_api.create_flex_flow` · `flex_v1_flex_flow_api.delete_flex_flow` · `flex_v1_flex_flow_api.fetch_flex_flow` · `flex_v1_flex_flow_api.list_flex_flow` · `flex_v1_flex_flow_api.update_flex_flow` · `flex_v1_insights_assessments_comment_api.create_insights_assessments_comment` · `flex_v1_insights_assessments_comment_api.list_insights_assessments_comment` · `flex_v1_insights_conversations_api.list_insights_conversations` · `flex_v1_insights_questionnaires_api.create_insights_questionnaires` · `flex_v1_insights_questionnaires_api.delete_insights_questionnaires` · `flex_v1_insights_questionnaires_api.fetch_insights_questionnaires` · `flex_v1_insights_questionnaires_api.list_insights_questionnaires` · `flex_v1_insights_questionnaires_api.update_insights_questionnaires` · `flex_v1_insights_questionnaires_category_api.create_insights_questionnaires_category` · `flex_v1_insights_questionnaires_category_api.delete_insights_questionnaires_category` · `flex_v1_insights_questionnaires_category_api.list_insights_questionnaires_category` · `flex_v1_insights_questionnaires_category_api.update_insights_questionnaires_category` · `flex_v1_insights_questionnaires_question_api.create_insights_questionnaires_question` · `flex_v1_insights_questionnaires_question_api.delete_insights_questionnaires_question` · `flex_v1_insights_questionnaires_question_api.list_insights_questionnaires_question` · `flex_v1_insights_questionnaires_question_api.update_insights_questionnaires_question` · `flex_v1_insights_segments_api.list_insights_segments` · `flex_v1_insights_session_api.create_insights_session` · `flex_v1_insights_settings_answer_sets_api.fetch_insights_settings_answersets` · `flex_v1_insights_settings_comment_api.fetch_insights_settings_comment` · `flex_v1_insights_user_roles_api.fetch_insights_user_roles` · `flex_v1_interaction_api.create_interaction` · `flex_v1_interaction_api.fetch_interaction2` · `flex_v1_interaction_api.update_interaction` · `flex_v1_interaction_channel.fetch_interaction_channel` · `flex_v1_interaction_channel.list_interaction_channel` · `flex_v1_interaction_channel.update_interaction_channel` · `flex_v1_interaction_channel_invite.create_interaction_channel_invite` · `flex_v1_interaction_channel_invite.list_interaction_channel_invite` · `flex_v1_interaction_channel_participant.create_interaction_channel_participant` · `flex_v1_interaction_channel_participant.list_interaction_channel_participant` · `flex_v1_interaction_channel_participant.update_interaction_channel_participant` · `flex_v1_interaction_transfer.create_interaction_transfer` · `flex_v1_interaction_transfer.fetch_interaction_transfer` · `flex_v1_interaction_transfer.update_interaction_transfer` · `flex_v1_plugin_api.create_plugin` · `flex_v1_plugin_api.fetch_plugin` · `flex_v1_plugin_api.list_plugin` · `flex_v1_plugin_api.update_plugin` · `flex_v1_plugin_archive_api.update_plugin_archive` · `flex_v1_plugin_configuration_api.create_plugin_configuration` · `flex_v1_plugin_configuration_api.fetch_plugin_configuration` · `flex_v1_plugin_configuration_api.list_plugin_configuration` · `flex_v1_plugin_configuration_archive_api.update_plugin_configuration_archive` · `flex_v1_plugin_release_api.create_plugin_release` · `flex_v1_plugin_release_api.fetch_plugin_release` · `flex_v1_plugin_release_api.list_plugin_release` · `flex_v1_plugin_version_archive_api.update_plugin_version_archive` · `flex_v1_plugin_versions.create_plugin_version` · `flex_v1_plugin_versions.fetch_plugin_version` · `flex_v1_plugin_versions.list_plugin_version` · `flex_v1_provisioning_status_api.fetch_provisioning_status` · `flex_v1_web_channel_api.create_web_channel` · `flex_v1_web_channel_api.delete_web_channel` · `flex_v1_web_channel_api.fetch_web_channel` · `flex_v1_web_channel_api.list_web_channel` · `flex_v1_web_channel_api.update_web_channel` · `flex_v2_flex_user_api.fetch_flex_user` · `flex_v2_flex_user_api.update_flex_user` · `flex_v2_web_channels.create_web_channel2` · `insights_v1_annotation.fetch_annotation` · `insights_v1_annotation.update_annotation` · `insights_v1_call_api.fetch_call2` · `insights_v1_call_summaries_api.list_call_summaries` · `insights_v1_call_summary_api.fetch_summary` · `insights_v1_conference_api.fetch_conference2` · `insights_v1_conference_api.list_conference2` · `insights_v1_conference_participant.fetch_conference_participant` · `insights_v1_conference_participant.list_conference_participant` · `insights_v1_create_account_report.create_account_report` · `insights_v1_create_inbound_phone_numbers_report.create_inbound_phone_numbers_report` · `insights_v1_create_outbound_phone_numbers_report.create_outbound_phone_numbers_report` · `insights_v1_event.list_event2` · `insights_v1_get_account_report.fetch_account_report` · `insights_v1_get_inbound_phone_numbers_report.list_inbound_phone_numbers_report` · `insights_v1_get_outbound_phone_numbers_report.list_outbound_phone_numbers_report` · `insights_v1_metric.list_metric` · `insights_v1_participant.fetch_video_participant_summary` · `insights_v1_participant.list_video_participant_summary` · `insights_v1_room.fetch_video_room_summary` · `insights_v1_room.list_video_room_summary` · `insights_v1_setting.fetch_account_settings` · `insights_v1_setting.update_account_settings` · `lookups_v1_phone_number_api.fetch_phone_number2` · `lookups_v2_phone_number.fetch_phone_number3` · `messaging_v1_alpha_sender.create_alpha_sender` · `messaging_v1_alpha_sender.delete_alpha_sender` · `messaging_v1_alpha_sender.fetch_alpha_sender` · `messaging_v1_alpha_sender.list_alpha_sender` · `messaging_v1_brand_registration.create_brand_registrations` · `messaging_v1_brand_registration.fetch_brand_registrations` · `messaging_v1_brand_registration.list_brand_registrations` · `messaging_v1_brand_registration.update_brand_registrations` · `messaging_v1_brand_registration_otp.create_brand_registration_otp` · `messaging_v1_brand_vetting.create_brand_vetting` · `messaging_v1_brand_vetting.fetch_brand_vetting` · `messaging_v1_brand_vetting.list_brand_vetting` · `messaging_v1_channel_sender.create_channel_sender` · `messaging_v1_channel_sender.delete_channel_sender` · `messaging_v1_channel_sender.fetch_channel_sender` · `messaging_v1_channel_sender.list_channel_sender` · `messaging_v1_deactivations.fetch_deactivation` · `messaging_v1_destination_alpha_sender.create_destination_alpha_sender` · `messaging_v1_destination_alpha_sender.delete_destination_alpha_sender` · `messaging_v1_destination_alpha_sender.fetch_destination_alpha_sender` · `messaging_v1_destination_alpha_sender.list_destination_alpha_sender` · `messaging_v1_domain_certs.delete_domain_cert_v4` · `messaging_v1_domain_certs.fetch_domain_cert_v4` · `messaging_v1_domain_certs.update_domain_cert_v4` · `messaging_v1_domain_config_api.fetch_domain_config` · `messaging_v1_domain_config_api.update_domain_config` · `messaging_v1_domain_config_messaging_service_api.fetch_domain_config_messaging_service` · `messaging_v1_domain_validate_dns.fetch_domain_dns_validation` · `messaging_v1_external_campaign_api.create_external_campaign` · `messaging_v1_linkshortening_messaging_service_api.create_linkshortening_messaging_service` · `messaging_v1_linkshortening_messaging_service_api.delete_linkshortening_messaging_service` · `messaging_v1_linkshortening_messaging_service_domain_association_api.fetch_linkshortening_messaging_service_domain_association` · `messaging_v1_phone_number.create_phone_number` · `messaging_v1_phone_number.delete_phone_number` · `messaging_v1_phone_number.fetch_phone_number` · `messaging_v1_phone_number.list_phone_number` · `messaging_v1_request_managed_cert_api.update_request_managed_cert` · `messaging_v1_service_api.create_service` · `messaging_v1_service_api.delete_service` · `messaging_v1_service_api.fetch_service` · `messaging_v1_service_api.list_service` · `messaging_v1_service_api.update_service` · `messaging_v1_short_code.create_short_code` · `messaging_v1_short_code.delete_short_code` · `messaging_v1_short_code.fetch_short_code2` · `messaging_v1_short_code.list_short_code2` · `messaging_v1_tollfree_verification_api.create_tollfree_verification` · `messaging_v1_tollfree_verification_api.delete_tollfree_verification` · `messaging_v1_tollfree_verification_api.fetch_tollfree_verification` · `messaging_v1_tollfree_verification_api.list_tollfree_verification` · `messaging_v1_tollfree_verification_api.update_tollfree_verification` · `messaging_v1_us_app_to_person.create_us_app_to_person` · `messaging_v1_us_app_to_person.delete_us_app_to_person` · `messaging_v1_us_app_to_person.fetch_us_app_to_person` · `messaging_v1_us_app_to_person.list_us_app_to_person` · `messaging_v1_us_app_to_person.update_us_app_to_person` · `messaging_v1_us_app_to_person_usecase.fetch_us_app_to_person_usecase` · `messaging_v1_usecase_api.fetch_usecase` · `messaging_v2_channels_sender.create_channels_sender` · `messaging_v2_channels_sender.delete_channels_sender` · `messaging_v2_channels_sender.fetch_channels_sender` · `messaging_v2_channels_sender.list_channels_sender` · `messaging_v2_channels_sender.update_channels_sender` · `messaging_v2_domain_certs.fetch_domain_cert_v42` · `messaging_v2_typing_indicator.create_typing_indicator` · `numbers_v1_bulk_eligibility_api.create_bulk_eligibility` · `numbers_v1_bulk_eligibility_api.fetch_bulk_eligibility` · `numbers_v1_eligibility_api.create_eligibility` · `numbers_v1_porting_port_in_api.create_porting_port_in` · `numbers_v1_porting_port_in_api.delete_porting_port_in` · `numbers_v1_porting_port_in_api.fetch_porting_port_in` · `numbers_v1_porting_port_in_api.list_port_in_requests` · `numbers_v1_porting_port_in_phone_number_api.delete_porting_port_in_phone_number` · `numbers_v1_porting_port_in_phone_number_api.fetch_porting_port_in_phone_number` · `numbers_v1_porting_portability_api.fetch_porting_portability` · `numbers_v1_porting_webhook_configuration_api.create_porting_webhook_configuration` · `numbers_v1_porting_webhook_configuration_delete_api.delete_porting_webhook_configuration_delete` · `numbers_v1_porting_webhook_configuration_fetch_api.fetch_porting_webhook_configuration_fetch` · `numbers_v1_signing_request_configuration_api.create_signing_request_configuration` · `numbers_v1_signing_request_configuration_api.list_signing_request_configuration` · `numbers_v2_authorization_document_api.create_authorization_document` · `numbers_v2_authorization_document_api.delete_authorization_document` · `numbers_v2_authorization_document_api.fetch_authorization_document` · `numbers_v2_authorization_document_api.list_authorization_document` · `numbers_v2_bulk_hosted_number_order_api.create_bulk_hosted_number_order` · `numbers_v2_bulk_hosted_number_order_api.fetch_bulk_hosted_number_order` · `numbers_v2_bundle.create_bundle` · `numbers_v2_bundle.delete_bundle` · `numbers_v2_bundle.fetch_bundle` · `numbers_v2_bundle.list_bundle` · `numbers_v2_bundle.update_bundle` · `numbers_v2_bundle_clone_api.create_bundle_clone` · `numbers_v2_bundle_copy.create_bundle_copy` · `numbers_v2_bundle_copy.list_bundle_copy` · `numbers_v2_dependent_hosted_number_order.list_dependent_hosted_number_order` · `numbers_v2_end_user.create_end_user` · `numbers_v2_end_user.delete_end_user` · `numbers_v2_end_user.fetch_end_user` · `numbers_v2_end_user.list_end_user` · `numbers_v2_end_user.update_end_user` · `numbers_v2_end_user_type.fetch_end_user_type` · `numbers_v2_end_user_type.list_end_user_type` · `numbers_v2_evaluation.create_evaluation` · `numbers_v2_evaluation.fetch_evaluation` · `numbers_v2_evaluation.list_evaluation` · `numbers_v2_hosted_number_order_api.create_hosted_number_order` · `numbers_v2_hosted_number_order_api.delete_hosted_number_order` · `numbers_v2_hosted_number_order_api.fetch_hosted_number_order` · `numbers_v2_hosted_number_order_api.list_hosted_number_order` · `numbers_v2_hosted_number_order_api.update_hosted_number_order` · `numbers_v2_item_assignment.create_item_assignment` · `numbers_v2_item_assignment.delete_item_assignment` · `numbers_v2_item_assignment.fetch_item_assignment` · `numbers_v2_item_assignment.list_item_assignment` · `numbers_v2_regulation.fetch_regulation` · `numbers_v2_regulation.list_regulation` · `numbers_v2_replace_items.create_replace_items` · `numbers_v2_supporting_document.create_supporting_document` · `numbers_v2_supporting_document.delete_supporting_document` · `numbers_v2_supporting_document.fetch_supporting_document` · `numbers_v2_supporting_document.list_supporting_document` · `numbers_v2_supporting_document.update_supporting_document` · `numbers_v2_supporting_document_type.fetch_supporting_document_type` · `numbers_v2_supporting_document_type.list_supporting_document_type` · `numbers_v3_hosted_numbers_hosted_number_order_api.create_hosted_numbers_hosted_number_order` · `proxy_v1_interaction.delete_interaction` · `proxy_v1_interaction.fetch_interaction` · `proxy_v1_interaction.list_interaction` · `proxy_v1_message_interaction.create_message_interaction` · `proxy_v1_message_interaction.fetch_message_interaction` · `proxy_v1_message_interaction.list_message_interaction` · `proxy_v1_participant.create_participant2` · `proxy_v1_participant.delete_participant2` · `proxy_v1_participant.fetch_participant3` · `proxy_v1_participant.list_participant2` · `proxy_v1_phone_number.create_phone_number2` · `proxy_v1_phone_number.delete_phone_number2` · `proxy_v1_phone_number.fetch_phone_number4` · `proxy_v1_phone_number.list_phone_number2` · `proxy_v1_phone_number.update_phone_number` · `proxy_v1_service_api.create_service4` · `proxy_v1_service_api.delete_service4` · `proxy_v1_service_api.fetch_service4` · `proxy_v1_service_api.list_service4` · `proxy_v1_service_api.update_service3` · `proxy_v1_session.create_session` · `proxy_v1_session.delete_session` · `proxy_v1_session.fetch_session` · `proxy_v1_session.list_session` · `proxy_v1_session.update_session` · `studio_v1_engagement.create_engagement` · `studio_v1_engagement.delete_engagement` · `studio_v1_engagement.fetch_engagement` · `studio_v1_engagement.list_engagement` · `studio_v1_engagement_context.fetch_engagement_context` · `studio_v1_execution.create_execution` · `studio_v1_execution.delete_execution` · `studio_v1_execution.fetch_execution` · `studio_v1_execution.list_execution` · `studio_v1_execution.update_execution` · `studio_v1_execution_context.fetch_execution_context` · `studio_v1_execution_step.fetch_execution_step` · `studio_v1_execution_step.list_execution_step` · `studio_v1_execution_step_context.fetch_execution_step_context` · `studio_v1_flow_api.delete_flow` · `studio_v1_flow_api.fetch_flow` · `studio_v1_flow_api.list_flow` · `studio_v1_step.fetch_step` · `studio_v1_step.list_step` · `studio_v1_step_context.fetch_step_context` · `studio_v2_execution.create_execution2` · `studio_v2_execution.delete_execution2` · `studio_v2_execution.fetch_execution2` · `studio_v2_execution.list_execution2` · `studio_v2_execution.update_execution2` · `studio_v2_execution_context.fetch_execution_context2` · `studio_v2_execution_step.fetch_execution_step2` · `studio_v2_execution_step.list_execution_step2` · `studio_v2_execution_step_context.fetch_execution_step_context2` · `studio_v2_flow_api.create_flow` · `studio_v2_flow_api.delete_flow2` · `studio_v2_flow_api.fetch_flow2` · `studio_v2_flow_api.list_flow2` · `studio_v2_flow_api.update_flow` · `studio_v2_flow_revision.fetch_flow_revision` · `studio_v2_flow_revision.list_flow_revision` · `studio_v2_flow_test_user_api.fetch_test_user` · `studio_v2_flow_test_user_api.update_test_user` · `studio_v2_flow_validate_api.update_flow_validate` · `sync_v1_document.create_document` · `sync_v1_document.delete_document` · `sync_v1_document.fetch_document` · `sync_v1_document.list_document` · `sync_v1_document.update_document` · `sync_v1_document_permission.delete_document_permission` · `sync_v1_document_permission.fetch_document_permission` · `sync_v1_document_permission.list_document_permission` · `sync_v1_document_permission.update_document_permission` · `sync_v1_service_api.create_service5` · `sync_v1_service_api.delete_service5` · `sync_v1_service_api.fetch_service5` · `sync_v1_service_api.list_service5` · `sync_v1_service_api.update_service4` · `sync_v1_stream_message.create_stream_message` · `sync_v1_sync_list.create_sync_list` · `sync_v1_sync_list.delete_sync_list` · `sync_v1_sync_list.fetch_sync_list` · `sync_v1_sync_list.list_sync_list` · `sync_v1_sync_list.update_sync_list` · `sync_v1_sync_list_item.create_sync_list_item` · `sync_v1_sync_list_item.delete_sync_list_item` · `sync_v1_sync_list_item.fetch_sync_list_item` · `sync_v1_sync_list_item.list_sync_list_item` · `sync_v1_sync_list_item.update_sync_list_item` · `sync_v1_sync_list_permission.delete_sync_list_permission` · `sync_v1_sync_list_permission.fetch_sync_list_permission` · `sync_v1_sync_list_permission.list_sync_list_permission` · `sync_v1_sync_list_permission.update_sync_list_permission` · `sync_v1_sync_map.create_sync_map` · `sync_v1_sync_map.delete_sync_map` · `sync_v1_sync_map.fetch_sync_map` · `sync_v1_sync_map.list_sync_map` · `sync_v1_sync_map.update_sync_map` · `sync_v1_sync_map_item.create_sync_map_item` · `sync_v1_sync_map_item.delete_sync_map_item` · `sync_v1_sync_map_item.fetch_sync_map_item` · `sync_v1_sync_map_item.list_sync_map_item` · `sync_v1_sync_map_item.update_sync_map_item` · `sync_v1_sync_map_permission.delete_sync_map_permission` · `sync_v1_sync_map_permission.fetch_sync_map_permission` · `sync_v1_sync_map_permission.list_sync_map_permission` · `sync_v1_sync_map_permission.update_sync_map_permission` · `sync_v1_sync_stream.create_sync_stream` · `sync_v1_sync_stream.delete_sync_stream` · `sync_v1_sync_stream.fetch_sync_stream` · `sync_v1_sync_stream.list_sync_stream` · `sync_v1_sync_stream.update_sync_stream` · `taskrouter_v1_activity.create_activity` · `taskrouter_v1_activity.delete_activity` · `taskrouter_v1_activity.fetch_activity` · `taskrouter_v1_activity.list_activity` · `taskrouter_v1_activity.update_activity` · `taskrouter_v1_event.fetch_event` · `taskrouter_v1_event.list_event` · `taskrouter_v1_task.create_task` · `taskrouter_v1_task.delete_task` · `taskrouter_v1_task.fetch_task` · `taskrouter_v1_task.list_task` · `taskrouter_v1_task.update_task` · `taskrouter_v1_task_channel.create_task_channel` · `taskrouter_v1_task_channel.delete_task_channel` · `taskrouter_v1_task_channel.fetch_task_channel` · `taskrouter_v1_task_channel.list_task_channel` · `taskrouter_v1_task_channel.update_task_channel` · `taskrouter_v1_task_queue.create_task_queue` · `taskrouter_v1_task_queue.delete_task_queue` · `taskrouter_v1_task_queue.fetch_task_queue` · `taskrouter_v1_task_queue.list_task_queue` · `taskrouter_v1_task_queue.update_task_queue` · `taskrouter_v1_task_queue_bulk_real_time_statistics.create_task_queue_bulk_real_time_statistics` · `taskrouter_v1_task_queue_cumulative_statistics.fetch_task_queue_cumulative_statistics` · `taskrouter_v1_task_queue_real_time_statistics.fetch_task_queue_real_time_statistics` · `taskrouter_v1_task_queue_statistics.fetch_task_queue_statistics` · `taskrouter_v1_task_queues_statistics.list_task_queues_statistics` · `taskrouter_v1_task_reservation.fetch_task_reservation` · `taskrouter_v1_task_reservation.list_task_reservation` · `taskrouter_v1_task_reservation.update_task_reservation` · `taskrouter_v1_worker.create_worker` · `taskrouter_v1_worker.delete_worker` · `taskrouter_v1_worker.fetch_worker` · `taskrouter_v1_worker.list_worker` · `taskrouter_v1_worker.update_worker` · `taskrouter_v1_worker_channel.fetch_worker_channel` · `taskrouter_v1_worker_channel.list_worker_channel` · `taskrouter_v1_worker_channel.update_worker_channel` · `taskrouter_v1_worker_reservation.fetch_worker_reservation` · `taskrouter_v1_worker_reservation.list_worker_reservation` · `taskrouter_v1_worker_reservation.update_worker_reservation` · `taskrouter_v1_worker_statistics.fetch_worker_instance_statistics` · `taskrouter_v1_workers_cumulative_statistics.fetch_workers_cumulative_statistics` · `taskrouter_v1_workers_real_time_statistics.fetch_workers_real_time_statistics` · `taskrouter_v1_workers_statistics.fetch_worker_statistics` · `taskrouter_v1_workflow.create_workflow` · `taskrouter_v1_workflow.delete_workflow` · `taskrouter_v1_workflow.fetch_workflow` · `taskrouter_v1_workflow.list_workflow` · `taskrouter_v1_workflow.update_workflow` · `taskrouter_v1_workflow_cumulative_statistics.fetch_workflow_cumulative_statistics` · `taskrouter_v1_workflow_real_time_statistics.fetch_workflow_real_time_statistics` · `taskrouter_v1_workflow_statistics.fetch_workflow_statistics` · `taskrouter_v1_workspace_api.create_workspace` · `taskrouter_v1_workspace_api.delete_workspace` · `taskrouter_v1_workspace_api.fetch_workspace` · `taskrouter_v1_workspace_api.list_workspace` · `taskrouter_v1_workspace_api.update_workspace` · `taskrouter_v1_workspace_cumulative_statistics.fetch_workspace_cumulative_statistics` · `taskrouter_v1_workspace_real_time_statistics.fetch_workspace_real_time_statistics` · `taskrouter_v1_workspace_statistics.fetch_workspace_statistics` · `trusthub_v1_compliance_inquiries.create_compliance_inquiry` · `trusthub_v1_compliance_inquiries.update_compliance_inquiry` · `trusthub_v1_compliance_registration_inquiries.create_compliance_registration` · `trusthub_v1_compliance_registration_inquiries.update_compliance_registration` · `trusthub_v1_compliance_tollfree_inquiries.create_compliance_tollfree_inquiry` · `trusthub_v1_customer_profiles.create_customer_profile` · `trusthub_v1_customer_profiles.delete_customer_profile` · `trusthub_v1_customer_profiles.fetch_customer_profile` · `trusthub_v1_customer_profiles.list_customer_profile` · `trusthub_v1_customer_profiles.update_customer_profile` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.create_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.delete_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.fetch_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_channel_endpoint_assignment.list_customer_profile_channel_endpoint_assignment` · `trusthub_v1_customer_profiles_entity_assignments.create_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_entity_assignments.delete_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_entity_assignments.fetch_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_entity_assignments.list_customer_profile_entity_assignment` · `trusthub_v1_customer_profiles_evaluations.create_customer_profile_evaluation` · `trusthub_v1_customer_profiles_evaluations.fetch_customer_profile_evaluation` · `trusthub_v1_customer_profiles_evaluations.list_customer_profile_evaluation` · `trusthub_v1_end_user_api.create_end_user2` · `trusthub_v1_end_user_api.delete_end_user2` · `trusthub_v1_end_user_api.fetch_end_user2` · `trusthub_v1_end_user_api.list_end_user2` · `trusthub_v1_end_user_api.update_end_user2` · `trusthub_v1_end_user_type.fetch_end_user_type2` · `trusthub_v1_end_user_type.list_end_user_type2` · `trusthub_v1_policies_api.fetch_policies` · `trusthub_v1_policies_api.list_policies` · `trusthub_v1_supporting_document_api.create_supporting_document2` · `trusthub_v1_supporting_document_api.delete_supporting_document2` · `trusthub_v1_supporting_document_api.fetch_supporting_document2` · `trusthub_v1_supporting_document_api.list_supporting_document2` · `trusthub_v1_supporting_document_api.update_supporting_document2` · `trusthub_v1_supporting_document_type.fetch_supporting_document_type2` · `trusthub_v1_supporting_document_type.list_supporting_document_type2` · `trusthub_v1_trust_products.create_trust_product` · `trusthub_v1_trust_products.delete_trust_product` · `trusthub_v1_trust_products.fetch_trust_product` · `trusthub_v1_trust_products.list_trust_product` · `trusthub_v1_trust_products.update_trust_product` · `trusthub_v1_trust_products_channel_endpoint_assignment.create_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_channel_endpoint_assignment.delete_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_channel_endpoint_assignment.fetch_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_channel_endpoint_assignment.list_trust_product_channel_endpoint_assignment` · `trusthub_v1_trust_products_entity_assignments.create_trust_product_entity_assignment` · `trusthub_v1_trust_products_entity_assignments.delete_trust_product_entity_assignment` · `trusthub_v1_trust_products_entity_assignments.fetch_trust_product_entity_assignment` · `trusthub_v1_trust_products_entity_assignments.list_trust_product_entity_assignment` · `trusthub_v1_trust_products_evaluations.create_trust_product_evaluation` · `trusthub_v1_trust_products_evaluations.fetch_trust_product_evaluation` · `trusthub_v1_trust_products_evaluations.list_trust_product_evaluation` · `v2_short_code_applications.create_short_code_application` · `v2_short_code_applications.fetch_short_code_application` · `v2_short_code_applications.list_short_code_applications` · `verify_v2_access_token.create_access_token` · `verify_v2_access_token.fetch_access_token` · `verify_v2_bucket.create_bucket` · `verify_v2_bucket.delete_bucket` · `verify_v2_bucket.fetch_bucket` · `verify_v2_bucket.list_bucket` · `verify_v2_bucket.update_bucket` · `verify_v2_challenge.create_challenge` · `verify_v2_challenge.fetch_challenge` · `verify_v2_challenge.list_challenge` · `verify_v2_challenge.update_challenge` · `verify_v2_entity.create_entity` · `verify_v2_entity.delete_entity` · `verify_v2_entity.fetch_entity` · `verify_v2_entity.list_entity` · `verify_v2_factor.delete_factor` · `verify_v2_factor.fetch_factor` · `verify_v2_factor.list_factor` · `verify_v2_factor.update_factor` · `verify_v2_form_api.fetch_form` · `verify_v2_messaging_configuration.create_messaging_configuration` · `verify_v2_messaging_configuration.delete_messaging_configuration` · `verify_v2_messaging_configuration.fetch_messaging_configuration` · `verify_v2_messaging_configuration.list_messaging_configuration` · `verify_v2_messaging_configuration.update_messaging_configuration` · `verify_v2_new_challenge.create_challenge_passkeys` · `verify_v2_new_factor.create_new_factor` · `verify_v2_new_factor.create_new_factor_passkey` · `verify_v2_notification.create_notification` · `verify_v2_rate_limit.create_rate_limit` · `verify_v2_rate_limit.delete_rate_limit` · `verify_v2_rate_limit.fetch_rate_limit` · `verify_v2_rate_limit.list_rate_limit` · `verify_v2_rate_limit.update_rate_limit` · `verify_v2_safelist_api.create_safelist` · `verify_v2_safelist_api.delete_safelist` · `verify_v2_safelist_api.fetch_safelist` · `verify_v2_service_api.create_service2` · `verify_v2_service_api.delete_service2` · `verify_v2_service_api.fetch_service2` · `verify_v2_service_api.list_service2` · `verify_v2_service_api.update_service2` · `verify_v2_template.list_verification_template` · `verify_v2_verification.fetch_verification` · `verify_v2_verification.update_verification` · `verify_v2_verification_attempt_api.fetch_verification_attempt` · `verify_v2_verification_attempt_api.list_verification_attempt` · `verify_v2_verification_attempts_summary_api.fetch_verification_attempts_summary` · `verify_v2_verification_check.create_verification_check` · `verify_v2_webhook.create_webhook` · `verify_v2_webhook.delete_webhook` · `verify_v2_webhook.fetch_webhook` · `verify_v2_webhook.list_webhook` · `verify_v2_webhook.update_webhook` · `video_v1_anonymize.update_room_participant_anonymize` · `video_v1_composition_api.create_composition` · `video_v1_composition_api.delete_composition` · `video_v1_composition_api.fetch_composition` · `video_v1_composition_api.list_composition` · `video_v1_composition_hook_api.create_composition_hook` · `video_v1_composition_hook_api.delete_composition_hook` · `video_v1_composition_hook_api.fetch_composition_hook` · `video_v1_composition_hook_api.list_composition_hook` · `video_v1_composition_hook_api.update_composition_hook` · `video_v1_composition_settings_api.create_composition_settings` · `video_v1_composition_settings_api.fetch_composition_settings` · `video_v1_participant.fetch_room_participant` · `video_v1_participant.list_room_participant` · `video_v1_participant.update_room_participant` · `video_v1_published_track.fetch_room_participant_published_track` · `video_v1_published_track.list_room_participant_published_track` · `video_v1_recording_api.delete_recording2` · `video_v1_recording_api.fetch_recording2` · `video_v1_recording_api.list_recording2` · `video_v1_recording_rules.fetch_room_recording_rule` · `video_v1_recording_rules.update_room_recording_rule` · `video_v1_recording_settings_api.create_recording_settings` · `video_v1_recording_settings_api.fetch_recording_settings` · `video_v1_room_api.create_room` · `video_v1_room_api.fetch_room` · `video_v1_room_api.list_room` · `video_v1_room_api.update_room` · `video_v1_room_recording.delete_room_recording` · `video_v1_room_recording.fetch_room_recording` · `video_v1_room_recording.list_room_recording` · `video_v1_subscribe_rules.fetch_room_participant_subscribe_rule` · `video_v1_subscribe_rules.update_room_participant_subscribe_rule` · `video_v1_subscribed_track.fetch_room_participant_subscribed_track` · `video_v1_subscribed_track.list_room_participant_subscribed_track` · `video_v1_transcriptions.create_room_transcriptions` · `video_v1_transcriptions.fetch_room_transcriptions` · `video_v1_transcriptions.list_room_transcriptions` · `video_v1_transcriptions.update_room_transcriptions`: no typed arm, so `.error` is always `RawError` + 9. So `isinstance(e.error, AccountsCallsRecordingsSidJson201041408Error1)` matches only 34 of 898 operations. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 23 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which server each call resolves against**, set through `server_config`. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. + diff --git a/plugins/vimeo/.claude-plugin/plugin.json b/plugins/vimeo/.claude-plugin/plugin.json index ee734152..62af10ca 100644 --- a/plugins/vimeo/.claude-plugin/plugin.json +++ b/plugins/vimeo/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "vimeo", "displayName": "Vimeo API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Vimeo API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/vimeo/.cursor-plugin/plugin.json b/plugins/vimeo/.cursor-plugin/plugin.json index ee734152..62af10ca 100644 --- a/plugins/vimeo/.cursor-plugin/plugin.json +++ b/plugins/vimeo/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "vimeo", "displayName": "Vimeo API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Vimeo API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/vimeo/README.md b/plugins/vimeo/README.md index cb6475b2..ec8d2b1d 100644 --- a/plugins/vimeo/README.md +++ b/plugins/vimeo/README.md @@ -1,13 +1,13 @@ -# Vimeo API SDK Plugin - -A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Vimeo API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. - -## What's inside - -One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. - -| Language | Skill prefix | Skills | -| --- | --- | --- | -| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-vimeo`, `dotnet-models`, `dotnet-testing` | -| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-vimeo`, `python-models`, `python-testing` | -| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-vimeo`, `typescript-models`, `typescript-testing` | +# Vimeo API SDK Plugin + +A plugin whose skills teach a coding agent to install and use the APIMatic-generated **Vimeo API SDK**, in C#/.NET, python, TypeScript. Every SDK fact the skills state is grounded in the SDK's own source and generated documentation, not in what a model remembers about this API. + +## What's inside + +One skill set per language. The entry point is that language's getting-started skill, which carries what is specific to this SDK; the rest are API-agnostic and describe how to use any SDK the same generator produces. + +| Language | Skill prefix | Skills | +| --- | --- | --- | +| C#/.NET | `dotnet-` | `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-client-initialization`, `dotnet-configuration-resilience`, `dotnet-error-handling`, `dotnet-getting-started`, `dotnet-integrate-vimeo`, `dotnet-models`, `dotnet-testing` | +| python | `python-` | `python-authentication`, `python-calling-endpoints`, `python-client-initialization`, `python-configuration-resilience`, `python-error-handling`, `python-getting-started`, `python-integrate-vimeo`, `python-models`, `python-testing` | +| TypeScript | `typescript-` | `typescript-authentication`, `typescript-calling-endpoints`, `typescript-client-initialization`, `typescript-configuration-resilience`, `typescript-error-handling`, `typescript-getting-started`, `typescript-integrate-vimeo`, `typescript-models`, `typescript-testing` | diff --git a/plugins/vimeo/plugin.json b/plugins/vimeo/plugin.json index ee734152..62af10ca 100644 --- a/plugins/vimeo/plugin.json +++ b/plugins/vimeo/plugin.json @@ -1,7 +1,7 @@ { "name": "vimeo", "displayName": "Vimeo API", - "version": "0.3.0", + "version": "0.3.1", "description": "Integrate and consume the APIMatic-generated Vimeo API SDK — bundled skills for C#/.NET, python, TypeScript.", "author": { "name": "APIMatic", diff --git a/plugins/vimeo/skills/dotnet/dotnet-getting-started/SKILL.md b/plugins/vimeo/skills/dotnet/dotnet-getting-started/SKILL.md index 1b4b1ddb..6e29b08e 100644 --- a/plugins/vimeo/skills/dotnet/dotnet-getting-started/SKILL.md +++ b/plugins/vimeo/skills/dotnet/dotnet-getting-started/SKILL.md @@ -1,145 +1,145 @@ ---- -name: "dotnet-getting-started" -description: "Vimeo API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-vimeo-api is the workflow that uses it." ---- - -# Getting started with the Vimeo API .NET SDK - -> **Who this skill is for.** This is the **map layer**: load it to ground every Vimeo API SDK fact. `dotnet-integrate-vimeo-api` is the workflow that drives it — you collect the contracts for the work in scope into `vimeo-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. - -**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. - -> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. - -## SDK identity - -| | | -| --- | --- | -| API | Vimeo API | -| NuGet package | *not published to NuGet — see *Install* below* | -| Source repo | https://github.com/context-plugins/vimeo-csharp-sdk (branch `main` — the ref this map documents) | -| Root namespace | `VimeoApi` (the `using` namespace) | -| Client class | `VimeoApiClient` | -| Options class | `VimeoApiClientOptions` | -| Auth | **Bearer** token — set `Bearer`
**OAuth 2.0** authorization code — set `Oauth2AuthorizationCode`
**OAuth 2.0** client credentials — set `Oauth2ClientCredentials` | -| Environments | `ServerEnvironment.Production` *(default)* → `https://api.vimeo.com` | -| Log environment variable | `VIMEOAPICLIENT_LOG` | -| Target framework | `netstandard2.0` (C# `LangVersion 14`) | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-vimeo-api` makes that binding); the **Integration workflow** below says which governs which step. - -## Namespaces (using-directives) - -The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using VimeoApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: - -| Type | Namespace | -| --- | --- | -| `VimeoApiClient` | `VimeoApi` | -| `VimeoApiClientOptions` | `VimeoApi` | -| `ServerEnvironment` | `VimeoApi.Servers` | - -Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. - -## Install — from source - -This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Vimeo API, then write the `using` directives from the table above. - -> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. - -## SDK map — look up first, open the named file second, never grep - -The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): - -- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. -- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. -- **Shapes live in the source, one type per file.** Every file under `src/VimeoApi/Models/`, `src/VimeoApi/Errors/` and `src/VimeoApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/VimeoApi/Models/`, enums `src/VimeoApi/Models/Enums/`, typed errors `src/VimeoApi/Errors/`, request records `src/VimeoApi/Requests/`, or `src/VimeoApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. - -**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: - -1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. -2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. -3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. -4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `vimeo-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. -- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. -- Never open the SDK's `api-reference.md` — the map supersedes it. - -## SDK source — get it first; the map lives inside it - -The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/vimeo-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. - -**Clone once, up front**, into a fresh timestamped folder under `/vimeo-api-sdk-src/`, and reuse that folder for the rest of your session: - -```bash -# Linux/macOS: -dir="${TMPDIR:-/tmp}/vimeo-api-sdk-src/$(date +%Y%m%d-%H%M%S)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/vimeo-csharp-sdk "$dir" -# Reuse "$dir" for the rest of your session (it is your clone path). -``` - -```powershell -# Windows (PowerShell): -$dir = "$env:TEMP\vimeo-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" -git clone --filter=blob:none --branch main https://github.com/context-plugins/vimeo-csharp-sdk $dir -# Reuse $dir for the rest of your session (it is your clone path). -``` - -The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. - -The clone stays in the system temp directory, and `vimeo-api-plan.md` never carries its path — the plan must stay portable. - -Then confirm the SDK shape **only** from the clone — not by either of these: - -- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. -- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. - -Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): - -- `src/VimeoApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. -- `src/VimeoApi/Models/` (+ `src/VimeoApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. -- `src/VimeoApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/VimeoApi/Requests//` (an operation with several tags is filed under the first tag it declares). -- `src/VimeoApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). -- `src/VimeoApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). -- `src/VimeoApi/Servers/`, `src/VimeoApi/VimeoApiClient.cs`, `src/VimeoApi/ServiceCollectionExtensions.cs` — environments, the client, DI. - -**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. - -## Idempotency — the real keys are on the map; the injected header is not one - -The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Vimeo API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. - -Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. - -## Sensitive data — check the request models before you log anything - -Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `VIMEOAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. - -Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. - -## Response metadata — status and headers are not on the return value - -On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). - -**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. - -## The `dotnet-*` skill names are not unique across plugins - -Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/VimeoApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. - -## Integration workflow — load the companion skill at each step - -You loaded every companion the sheet named before starting (per `dotnet-integrate-vimeo-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new VimeoApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) -2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `VimeoApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) -3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) -4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) -5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) -6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) -7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) - +--- +name: "dotnet-getting-started" +description: "Vimeo API .NET SDK identity and lookup layer (.NET/C# only) you load directly — install, root namespace, environments, auth pattern, and the SDK map (shipped inside the SDK source repo): every operation signature and error case by lookup, plus the source file declaring each model, enum and error type. Load it for every contract fact, never memory; dotnet-integrate-vimeo-api is the workflow that uses it." +--- + +# Getting started with the Vimeo API .NET SDK + +> **Who this skill is for.** This is the **map layer**: load it to ground every Vimeo API SDK fact. `dotnet-integrate-vimeo-api` is the workflow that drives it — you collect the contracts for the work in scope into `vimeo-api-plan.md`, implement from that sheet, and come back to the one map page a row cites for any fact the sheet is missing. Every contract fact comes from here, never from memory. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated .NET SDK (auth, calling endpoints, models, error handling, retries, testing), see the companion API-agnostic skills: `dotnet-client-initialization`, `dotnet-authentication`, `dotnet-calling-endpoints`, `dotnet-models`, `dotnet-error-handling`, `dotnet-configuration-resilience`, `dotnet-testing`. + +**The SDK map and these companion skills are complementary — load both.** The map (generated from the SDK source, which remains the ground truth) is authoritative for the SDK's *surface* (signatures, model shapes, enums, which error type an operation throws); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading the map or source does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the map. + +> **Ground every signature, model, enum, and error type in the SDK map** (`sdk-map.md` + `map/operations/`, inside the SDK source repo — see the *SDK source* section for obtaining it). The map is **method-first**: operation signatures, error cases with their `TryGet…` accessors, pagination and server/auth wiring are on its pages by lookup — and for every type an operation names, the map names the **file that declares it**, so a shape question (fields, wire names, enum values, union accessors) is one targeted file open, never a search. When a map-sourced name fails to compile, re-read the file its row names; **never fill the gap from memory.** Do **not** decompile or reflect over the installed reference, do **not** fetch SDK files from the web ad hoc, and do **not** grep or run other expensive searches over the clone. + +## SDK identity + +| | | +| --- | --- | +| API | Vimeo API | +| NuGet package | *not published to NuGet — see *Install* below* | +| Source repo | https://github.com/context-plugins/vimeo-csharp-sdk (branch `main` — the ref this map documents) | +| Root namespace | `VimeoApi` (the `using` namespace) | +| Client class | `VimeoApiClient` | +| Options class | `VimeoApiClientOptions` | +| Auth | **Bearer** token — set `Bearer`
**OAuth 2.0** authorization code — set `Oauth2AuthorizationCode`
**OAuth 2.0** client credentials — set `Oauth2ClientCredentials` | +| Environments | `ServerEnvironment.Production` *(default)* → `https://api.vimeo.com` | +| Log environment variable | `VIMEOAPICLIENT_LOG` | +| Target framework | `netstandard2.0` (C# `LangVersion 14`) | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, namespaces, the auth *pattern*, the environments), while the actual integration code comes from the companion skills. Load every one the contract sheet's REQUIRED READING names before you start implementing (`dotnet-integrate-vimeo-api` makes that binding); the **Integration workflow** below says which governs which step. + +## Namespaces (using-directives) + +The SDK splits its public types across **separate child namespaces**. C# does **not** import child namespaces transitively, so `using VimeoApi.Models;` alone does **not** make enums, union types, or error types visible — you get `CS0103`/`CS0246` ("name/type does not exist") on build. Add a separate `using` for each kind of type you reference: + +| Type | Namespace | +| --- | --- | +| `VimeoApiClient` | `VimeoApi` | +| `VimeoApiClientOptions` | `VimeoApi` | +| `ServerEnvironment` | `VimeoApi.Servers` | + +Models, enums, union types and the generated error types each sit in their own child namespace. The map's **Namespaces by content type** table maps each source directory to its namespace, so read the namespace off the path the map gives for the type; anything left ambiguous is settled by the `namespace` declaration at the top of the type's own file. + +## Install — from source + +This SDK is **not published to a package feed**, so there is no `dotnet add package` for it. Build it from its repository — , branch `main` — and reference the resulting assembly from the project that will call Vimeo API, then write the `using` directives from the table above. + +> **Build against the assembly built from that repository, and do not copy the SDK source into your repository.** A copy is a fork: the next time the SDK is regenerated the copy stays behind, and the map then describes code you are not building against. The map is read from the read-only **reference clone** the *SDK source* section below describes, which lives in the system temp directory, is obtained at the start of SDK work and is never part of the build. + +## SDK map — look up first, open the named file second, never grep + +The SDK ships a generated map **inside the SDK source repo**, at the SDK root — the map and the SDK regenerate together in one repo, so they cannot drift apart. The map is **method-first**: operation contracts are on its pages; model, enum and error **shapes are deliberately not duplicated there** — the map names the file declaring each type, and you read the shape in that file. Every path below is relative to the SDK root in the clone — obtain it first (next section): + +- **`sdk-map.md`** — the index and the read-once pages: SDK identity, client construction and the full options surface, the error-handling model (Case A typed / Case B raw), the **defaults table** for operations, the model/enum/error directory conventions, *Namespaces by content type*, and *Servers & auth*. +- **`map/operations/`** — one page per controller, one block per operation: the exact C# signature with its request record's required members, the return type, the error case (A: typed `{Operation}Error` with its `TryGet…` accessors and their statuses / B: `RawError`), pagination and server-group bullets **only where they differ from the defaults**, and a **Type sources** table naming the file that declares every type the operation mentions. +- **Shapes live in the source, one type per file.** Every file under `src/VimeoApi/Models/`, `src/VimeoApi/Errors/` and `src/VimeoApi/Requests/` declares exactly one public type named after the file, so a type name *is* its path: take it from the operation's Type sources table (or the kind's directory — records `src/VimeoApi/Models/`, enums `src/VimeoApi/Models/Enums/`, typed errors `src/VimeoApi/Errors/`, request records `src/VimeoApi/Requests/`, or `src/VimeoApi/Requests//` for an operation on a controller; an operation with several tags is filed under the first tag it declares) and read the fields, `required` flags, enum values and, on a model, the `[JsonPropertyName]` wire names from that one file (a request record is never serialized and carries none). The compiler is the backstop: a wrong name fails to build. + +**This map is how you traverse the SDK.** Do **not** grep, glob, `find`, or otherwise scan the clone to locate an operation, model, enum, union, or error type — the map (and the name-is-path convention) is the locator. Instead: + +1. Open `sdk-map.md` and follow its controller table to the operations pages in scope; read every contract fact (signature, error accessors, pagination) by lookup. +2. **Silence means the default.** The index's defaults table states what holds for every operation (throw-only, no pagination, the default server group, inputs as one request record); an operation's block states only what differs. A block silent on pagination has none — take the default and move on rather than opening the source to confirm it. +3. For a **shape** (model fields, enum values, union accessors, a typed error's payload), open the one declaring file the map names — that is the normal flow here, not a fallback — and read it scoped. +4. The **HTTP verb/route and the endpoint's behavioural prose** live on the operation method itself — the XML `` docs in the `Api/{Controller}.cs` file the page header names; what each input means is in the per-member docs on its request record. Read them there when wire-level detail or call-acceptance semantics matter; never reconstruct a route from memory. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signatures and error accessors from the map pages, then required fields with wire names and enum values from the declaring files those pages name — into the **contract sheet** in `vimeo-api-plan.md`, then implement from the sheet. Do not re-open a map page or source file per field, and never re-look-up a fact the sheet already carries. +- When you do open a source file, read it **scoped**: with an offset/limit, or a search for the one symbol — never dump a whole file into the conversation. +- Never open the SDK's `api-reference.md` — the map supersedes it. + +## SDK source — get it first; the map lives inside it + +The SDK source is where the map lives, so **getting it is the first step of SDK work this session, not a fallback**: one shallow clone at the recorded ref, into the **system temp directory** (`/vimeo-api-sdk-src/`), reused for the whole session. It is a read-only reference, never in the project repo, and **not** a build dependency. Opening the **one declaring file** a map page names is the normal flow here — that is where model, enum and error shapes live. What stays off-limits is *locating* anything by scanning: every file you open is one the map named, opened scoped, never found by grep. + +**Clone once, up front**, into a fresh timestamped folder under `/vimeo-api-sdk-src/`, and reuse that folder for the rest of your session: + +```bash +# Linux/macOS: +dir="${TMPDIR:-/tmp}/vimeo-api-sdk-src/$(date +%Y%m%d-%H%M%S)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/vimeo-csharp-sdk "$dir" +# Reuse "$dir" for the rest of your session (it is your clone path). +``` + +```powershell +# Windows (PowerShell): +$dir = "$env:TEMP\vimeo-api-sdk-src\$(Get-Date -Format yyyyMMdd-HHmmss)" +git clone --filter=blob:none --branch main https://github.com/context-plugins/vimeo-csharp-sdk $dir +# Reuse $dir for the rest of your session (it is your clone path). +``` + +The source you read is branch `main`: a branch is a moving target, not a pin — it can be regenerated after this plugin ships, and then the map you read describes code the package no longer matches. If a name from the map fails to compile, trust the compiler, re-read the source file the map's row names, and report the mismatch; never patch around it from memory. + +The clone stays in the system temp directory, and `vimeo-api-plan.md` never carries its path — the plan must stay portable. + +Then confirm the SDK shape **only** from the clone — not by either of these: + +- **Do not decompile or run reflection over the built assembly.** A compiled assembly drops what the source carries and the other skills rely on: the XML ``/parameter doc-comments, the exact parameter names and order, and each method's internal request-builder list. +- **Do not fetch SDK files from the web one at a time** as your way in — `…/blob/…` pages return HTML (not source) and guessed paths fail, which is exactly how ad-hoc fetching breaks. Read the local clone instead; only if you truly cannot clone (`git` is unavailable), fetch a **raw** URL derived from the source repo above — never a `…/blob/…` page. + +Layout — where the SDK map's file references resolve, relative to the SDK root in the clone (open these directly; do not scan for them): + +- `src/VimeoApi/Api/` — one file per controller/group; **this is where the operation methods live** (the HTTP verb and route, and the XML `` docs carrying the endpoint's behavioural prose). The map's per-controller pages name the exact file. +- `src/VimeoApi/Models/` (+ `src/VimeoApi/Models/Enums/`) — body and response models, unions and enums; one public type per file, named after the file. +- `src/VimeoApi/Requests/` — one request record per operation that takes input, carrying the per-member docs for each input; a controller's records sit in `src/VimeoApi/Requests//` (an operation with several tags is filed under the first tag it declares). +- `src/VimeoApi/Errors/` — per-operation `{Operation}Error` types (only Case-A operations have one; the map's rows say which case each operation is). +- `src/VimeoApi/Core/` — HTTP infrastructure (the `SdkException` family, `RawError`, auth, retries, `SdkHook`). +- `src/VimeoApi/Servers/`, `src/VimeoApi/VimeoApiClient.cs`, `src/VimeoApi/ServiceCollectionExtensions.cs` — environments, the client, DI. + +**Leave the clone where it sits — do not delete it, copy it or edit it.** It is a read-only reference with nothing of yours in it, and keeping it is what lets every later step in this session reuse it instead of cloning again. An edit made in it never reaches the SDK, so report what needs changing instead of patching it; the OS reaps the temp directory on its own, and a future session simply makes its own timestamped clone. + +## Idempotency — the real keys are on the map; the injected header is not one + +The generator injects an `Idempotency-Key: Guid.NewGuid()` header on **every non-GET operation that does not already declare that header itself** — fresh on every call, invisible to you, and **not** an idempotency key in any meaningful sense: a value that changes per call deduplicates nothing, and Vimeo API may not document reading that header at all. `dotnet-configuration-resilience` explains why a visible header like this is worse than an absent one. + +Whether an operation takes a **real**, caller-supplied key is an API fact, visible on that operation's request record: look for a key-shaped member (an idempotency key, a request id) in the record file its **Type sources** table names. Its semantics — retention windows, when it is mandatory — are provider prose, in that member's XML docs and the method's `` in the `Api/` file the page names. Some APIs expose **`If-Match` optimistic concurrency** instead — a different guarantee (reject my write if the resource changed) solving a different problem (lost updates, not duplicates); those members show on the request record too. For every write with no key, the answer is reconciliation — `dotnet-configuration-resilience` — not hope. + +## Sensitive data — check the request models before you log anything + +Whether this API's **request** models carry fields you must never log — card or bank numbers, personal data, message content — is a shape question: each request model's declaring file (the map's Type sources table names it) lists every field with its `[JsonPropertyName]` wire name. If the scope touches such a field, three generator facts decide your logging posture (all in `dotnet-configuration-resilience`): JSON request bodies are logged **verbatim** when `LogRequestBody` is on, with no redaction; form bodies are masked only by deny-list; and leaving `options.Logging.LoggerFactory` unset arms the `VIMEOAPICLIENT_LOG` environment variable, whose `trace` level forces body logging on with no code change and no deploy. So on any build that can carry such a field: `LogRequestBody` stays off, `LoggerFactory` is set explicitly in production, and your own diagnostics never echo a request body on those paths. + +Responses are often safer — many APIs return masked variants of what requests carry raw — but that too is a model-shape fact: check the response model's file before assuming. + +## Response metadata — status and headers are not on the return value + +On a success the SDK returns only the deserialized body: the HTTP status and headers of a successful call are **not on the return value**, and unless the map's error-handling model notes no-throw `{Operation}Result` siblings for this SDK, there is no non-throwing variant to get them from. On an error, what is reachable depends on the operation's error case — its map row: a Case B `RawError` carries `StatusCode`; a typed Case A body may or may not (see `dotnet-error-handling`). + +**Hooks are the SDK's own seam for transport metadata.** `options.Hooks` (client-wide) and `requestOptions.Hooks` (per call, appended after the client-wide list) take `SdkHook` instances; `SdkHook.OnResponse((response, context) => …)` sees every raw `HttpResponseMessage` — status, `Retry-After`, rate-limit budget headers, a request-id echo for correlation — and `SdkHook.OnRequest` the outgoing request. A hook runs **inside the retry pipeline, once per attempt**, so "the" value is the last attempt's; a per-call hook can close over a local, which puts the observed value in scope in the same method's `catch`. `RawClient.ExecuteResult` does return an `ApiResult`, but `RawClient` is `internal sealed`, so hooks — not reflection, and not a `DelegatingHandler` you would otherwise need — are the supported way in. + +## The `dotnet-*` skill names are not unique across plugins + +Every APIMatic .NET plugin ships this same set of `dotnet-*` skill names. Copies from the **same generator version** are byte-identical, so between those plugins a collision is harmless. A copy from a **different generator version** is not — it describes a different runtime, and an older plugin's retry eligibility, request-options surface and unknown-field handling all differ from this SDK's. **Load the copy that ships with THIS plugin**: write plugin-qualified names where your harness supports them, and where it does not, confirm the copy you loaded describes the runtime this SDK actually ships — `RequestOptions` carrying both a log level and `Hooks`, and a `src/VimeoApi/Core/Hooks/SdkHook.cs` in the source. The `X-APIMatic-Gen-Version` wire header does not distinguish these surfaces, so it is not the check. + +## Integration workflow — load the companion skill at each step + +You loaded every companion the sheet named before starting (per `dotnet-integrate-vimeo-api`); before you write the code for each step, re-read the one that governs it — even if you have already read the relevant source. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client & DI setup** — load **dotnet-client-initialization** before you write `new VimeoApiClient(...)`, build its options, or DI-register it. (*The signature will not tell you:* the `HttpClient`/handler pipeline must be long-lived and reused via `IHttpClientFactory`, not rebuilt per request; the SDK client wrapper over it may be transient.) +2. **Authentication** — load **dotnet-authentication** before you set credentials. The scheme(s) this SDK accepts are the credentials properties on `VimeoApiClientOptions` — the identity table above names them. (*The signature will not tell you:* set credentials before constructing the client or in the DI callback, and load secrets from configuration rather than hardcoding.) +3. **Calling an endpoint / building a request body** — load **dotnet-calling-endpoints** before the first `client.{ApiGroup}.{Operation}(...)` call. (*The signature will not tell you:* every input goes on the operation's request record — `required` members must be set, the rest are optional or already carry the spec's default; and whether a write takes a real idempotency key is on that record — the injected `Idempotency-Key` header is not one, see *Idempotency* above.) +4. **Models** — load **dotnet-models** the moment a request/response field is not a plain string or number. (*The signature will not tell you:* unions are built with factory methods and read via `TryGet…` (no `new`), enums are `OpenStringEnum` / `OpenIntEnum` records with a generated `Match` — not C# enums, and with no public factory — and unknown response fields are kept on any model that carries an extension-data bag — conventionally `AdditionalProperties`, but a schema that closes itself with `additionalProperties: false` has none, and one that already declares a member of that name pushes the bag to a deduplicated spelling. Check the model's own map row rather than assuming the property is there.) +5. **Error handling** — load **dotnet-error-handling** before you write any `try/catch`. (*The signature will not tell you:* an operation is either Case B (`ApiException`, no typed accessors) or Case A (a typed `{Operation}Error`) — how the API mixes them is its own fact, so confirm each operation's case in its map row; and `TryGetRawError` is not a catch-all on the typed errors. Whether no-throw `…Result` variants exist at all is stated once, in the map's defaults table; never assume one does.) +6. **Configuration & resilience** — load **dotnet-configuration-resilience** when you tune retries, timeouts, the base URL, pagination, or logging. (*The signature will not tell you:* `HttpMethodsToRetry` gates **every** retry trigger, so a `POST` is never resent by default — but a `GET` that hangs costs far more than the `Timeout` value suggests, because the per-attempt timeout is itself retried. `Timeout` is per-attempt not total, `RetryOptions.Disabled()` turns retries off, and there **is** a built-in logger on `options.Logging` — whose `LogRequestBody` does not redact JSON.) +7. **Testing** — load **dotnet-testing** before you stub the SDK. (*The signature will not tell you:* the `HttpClient` constructor argument is the test seam; match the project's existing framework and assertion style.) + diff --git a/plugins/vimeo/skills/python/python-getting-started/SKILL.md b/plugins/vimeo/skills/python/python-getting-started/SKILL.md index cbcce64b..30bf73c1 100644 --- a/plugins/vimeo/skills/python/python-getting-started/SKILL.md +++ b/plugins/vimeo/skills/python/python-getting-started/SKILL.md @@ -1,341 +1,341 @@ ---- -name: "python-getting-started" -description: "Vimeo API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Vimeo API Python SDK contract question or writing any SDK code." ---- - -# Getting started with the Vimeo API Python SDK - -> **Who this skill is for.** This is the **lookup layer** for anyone writing Vimeo API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-vimeo-api` for the workflow that wraps this skill. - -This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. - -**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. - -## SDK identity - -Verified against `vimeo_api/` and `pyproject.toml` of the generated package at version `3.4.9`. **Re-verify after a version bump** — this page is a snapshot, not a live read. - -| Fact | Value | -| --- | --- | -| API | Vimeo API | -| Distribution name (what you install) | `vimeo-api` — **not on any package index**; installed from source (see *Install*) | -| Import root (what you import) | `vimeo_api` — not the string you install | -| Source repository | https://github.com/context-plugins/vimeo-python-sdk | -| Source branch | `main` | -| Version | `3.4.9` | -| Sync client class | `VimeoApiClient` (alias `Client`) | -| Async client class | `AsyncVimeoApiClient` (alias `AsyncClient`) | -| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `bearer` · `oauth2_authorization_code` · `oauth2_authorization_code_token_source` · `oauth2_client_credentials` · `oauth2_client_credentials_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | -| Auth | **Bearer** token — set `bearer` · **OAuth 2.0** authorization code — set `oauth2_authorization_code` · **OAuth 2.0** client credentials — set `oauth2_client_credentials` | -| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.vimeo.com` | -| Base-URL config | `ServerConfig` (`vimeo_api/server/server_config.py`), frozen, `extra="forbid"` | -| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | -| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | -| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | -| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | -| Surface | 520 operations across 100 controllers · 784 models · 1 union · 281 enums · 411 per-operation error unions | - -The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. - -## Install — from source - -Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: - -```bash -pip install "vimeo-api @ git+https://github.com/context-plugins/vimeo-python-sdk.git@main" -python -c "import vimeo_api, pathlib; print(pathlib.Path(vimeo_api.__file__).parent)" -``` - -**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. - -The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. - -Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. - -## Imports — the package splits its surface across four modules - -Python does not re-export child modules transitively, so `from vimeo_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. - -`vimeo_api/__init__.py` exports exactly 5 names beside the `vimeo_api.models` subpackage it re-exports: - -```python -from vimeo_api import ( - AsyncClient, - AsyncVimeoApiClient, - Client, - ServerConfig, - VimeoApiClient, -) -``` - -Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: - -| What you need | Where it lives | -| --- | --- | -| Domain models, their `…Dict` companions | `vimeo_api.models` | -| Enums (and their open `…OrStr`/`…OrInt` aliases) | `vimeo_api.models.enums` | -| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `AuthorizationCodeCredentials` · `ClientCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `vimeo_api.core` | -| Per-operation error *unions* | `vimeo_api.errors` (`ActivateLiveEventErrorBody`, …) | - -`vimeo_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). - -## Environments — there is no environment enum - -This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`vimeo_api/server/server_config.py`), and its default is **`https://api.vimeo.com`**: - -```python -base_url: str = "https://api.vimeo.com" -``` - -Consequences to state on every contract sheet that touches configuration: - -- Omitting `base_url` gives you **`https://api.vimeo.com`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. -- The token endpoint is derived from the same base URL (`/oauth/access_token`), so it moves only with `base_url` — you never configure it separately. -- The token endpoint is derived from the same base URL (`/oauth/authorize/client`), so it moves only with `base_url` — you never configure it separately. -- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. -- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. - -## Auth pattern (three schemes) - -A bearer token, exposed as the client's `bearer=` keyword taking a plain string. - -```python -from vimeo_api import Client - -client = Client(bearer="") -``` - -OAuth 2.0 authorization code, exposed as the client's `oauth2_authorization_code=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/oauth/access_token`. - -```python -from vimeo_api import Client -from vimeo_api.core import AuthorizationCodeCredentials - -def prompt(url: str) -> str: - return input(f"Open {url}, then paste the code: ") - -client = Client(oauth2_authorization_code=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) -client = Client(oauth2_authorization_code={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent -``` - -OAuth 2.0 client credentials, exposed as the client's `oauth2_client_credentials=` keyword taking `ClientCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/oauth/authorize/client`. - -```python -from vimeo_api import Client -from vimeo_api.core import ClientCredentials - -client = Client(oauth2_client_credentials=ClientCredentials(client_id="…", client_secret="…")) -client = Client(oauth2_client_credentials={"client_id": "…", "client_secret": "…"}) # equivalent -``` - -**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. - -See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. - -## Controllers - -Controllers and their operation counts (`client.`): - -| Attribute | Class | Ops | Area | -| --- | --- | --- | --- | -| `client.api_apps_webhooks` | `ApiAppsWebhooks` / `AsyncApiAppsWebhooks` | 5 | `add_webhook` · `delete_webhook` · `get_webhook` · `get_webhooks` · `update_webhook` | -| `client.api_information_essentials` | `ApiInformationEssentials` / `AsyncApiInformationEssentials` | 1 | `get_endpoints` | -| `client.authentication_extras_authenticate` | `AuthenticationExtrasAuthenticate` / `AsyncAuthenticationExtrasAuthenticate` | 1 | `client_auth` | -| `client.authentication_extras_convert` | `AuthenticationExtrasConvert` / `AsyncAuthenticationExtrasConvert` | 1 | `convert_access_token` | -| `client.authentication_extras_essentials` | `AuthenticationExtrasEssentials` / `AsyncAuthenticationExtrasEssentials` | 2 | `delete_token` · `verify_token` | -| `client.authentication_extras_exchange` | `AuthenticationExtrasExchange` / `AsyncAuthenticationExtrasExchange` | 1 | `exchange_auth_code` | -| `client.categories_channels` | `CategoriesChannels` / `AsyncCategoriesChannels` | 1 | `get_category_channels` | -| `client.categories_essentials` | `CategoriesEssentials` / `AsyncCategoriesEssentials` | 2 | `get_categories` · `get_category` | -| `client.categories_groups` | `CategoriesGroups` / `AsyncCategoriesGroups` | 1 | `get_category_groups` | -| `client.categories_users` | `CategoriesUsers` / `AsyncCategoriesUsers` | 8 | `check_if_user_subscribed_to_category` · `check_if_user_subscribed_to_category_alt1` · `get_category_subscriptions` · `get_category_subscriptions_alt1` · `subscribe_to_category` · `subscribe_to_category_alt1` · … | -| `client.categories_videos` | `CategoriesVideos` / `AsyncCategoriesVideos` | 4 | `check_category_for_video` · `get_category_videos` · `get_video_categories` · `suggest_video_category` | -| `client.channels_categories` | `ChannelsCategories` / `AsyncChannelsCategories` | 4 | `add_channel_categories` · `categorize_channel` · `delete_channel_category` · `get_channel_categories` | -| `client.channels_essentials` | `ChannelsEssentials` / `AsyncChannelsEssentials` | 7 | `create_channel` · `delete_channel` · `edit_channel` · `get_channel` · `get_channel_subscriptions` · `get_channel_subscriptions_alt1` · … | -| `client.channels_moderators` | `ChannelsModerators` / `AsyncChannelsModerators` | 7 | `add_channel_moderator` · `add_channel_moderators` · `get_channel_moderator` · `get_channel_moderators` · `remove_channel_moderator` · `remove_channel_moderators` · … | -| `client.channels_private_channel_members` | `ChannelsPrivateChannelMembers` / `AsyncChannelsPrivateChannelMembers` | 4 | `delete_channel_privacy_user` · `get_channel_privacy_users` · `set_channel_privacy_user` · `set_channel_privacy_users` | -| `client.channels_subscriptions_and_subscribers` | `ChannelsSubscriptionsAndSubscribers` / `AsyncChannelsSubscriptionsAndSubscribers` | 7 | `check_if_user_subscribed_to_channel` · `check_if_user_subscribed_to_channel_alt1` · `get_channel_subscribers` · `subscribe_to_channel` · `subscribe_to_channel_alt1` · `unsubscribe_from_channel` · … | -| `client.channels_tags` | `ChannelsTags` / `AsyncChannelsTags` | 5 | `add_channel_tag` · `add_tags_to_channel` · `check_if_channel_has_tag` · `delete_tag_from_channel` · `get_channel_tags` | -| `client.channels_videos` | `ChannelsVideos` / `AsyncChannelsVideos` | 7 | `add_video_to_channel` · `add_videos_to_channel` · `delete_video_from_channel` · `get_available_video_channels` · `get_channel_video` · `get_channel_videos` · … | -| `client.embed_presets_custom_logos` | `EmbedPresetsCustomLogos` / `AsyncEmbedPresetsCustomLogos` | 8 | `create_custom_logo` · `create_custom_logo_alt1` · `delete_custom_logo` · `delete_custom_logo_alt1` · `get_custom_logo` · `get_custom_logo_alt1` · … | -| `client.embed_presets_essentials` | `EmbedPresetsEssentials` / `AsyncEmbedPresetsEssentials` | 10 | `create_embed_presets` · `create_embed_presets_alt1` · `delete_embed_preset` · `delete_embed_preset_alt1` · `edit_embed_preset` · `edit_embed_preset_alt1` · … | -| `client.embed_presets_folders` | `EmbedPresetsFolders` / `AsyncEmbedPresetsFolders` | 1 | `delete_folder_embed_preset` | -| `client.embed_presets_timeline_events` | `EmbedPresetsTimelineEvents` / `AsyncEmbedPresetsTimelineEvents` | 2 | `create_video_custom_logo` · `get_video_custom_logo` | -| `client.embed_presets_videos` | `EmbedPresetsVideos` / `AsyncEmbedPresetsVideos` | 5 | `add_video_embed_preset` · `delete_video_embed_preset` · `get_embed_preset_videos` · `get_embed_preset_videos_alt1` · `get_video_embed_preset` | -| `client.folders_essentials` | `FoldersEssentials` / `AsyncFoldersEssentials` | 11 | `create_project` · `create_project_alt1` · `delete_project` · `delete_project_alt1` · `edit_project` · `edit_project_alt1` · … | -| `client.folders_items` | `FoldersItems` / `AsyncFoldersItems` | 3 | `delete_folder_items_alt1` · `get_folder_items` · `get_folder_items2` | -| `client.folders_videos` | `FoldersVideos` / `AsyncFoldersVideos` | 10 | `add_video_to_project` · `add_video_to_project_alt1` · `add_videos_to_project` · `add_videos_to_project_alt1` · `get_project_videos` · `get_project_videos_alt1` · … | -| `client.groups_essentials` | `GroupsEssentials` / `AsyncGroupsEssentials` | 4 | `create_group` · `delete_group` · `get_group` · `get_groups` | -| `client.groups_subscriptions` | `GroupsSubscriptions` / `AsyncGroupsSubscriptions` | 4 | `join_group` · `join_group_alt1` · `leave_group` · `leave_group_alt1` | -| `client.groups_users` | `GroupsUsers` / `AsyncGroupsUsers` | 5 | `check_if_user_joined_group` · `check_if_user_joined_group_alt1` · `get_group_members` · `get_user_groups` · `get_user_groups_alt1` | -| `client.groups_videos` | `GroupsVideos` / `AsyncGroupsVideos` | 5 | `add_video_to_group` · `delete_video_from_group` · `get_available_video_groups` · `get_group_video` · `get_group_videos` | -| `client.likes_essentials` | `LikesEssentials` / `AsyncLikesEssentials` | 11 | `check_if_user_liked_video` · `check_if_user_liked_video_alt1` · `get_likes` · `get_likes_alt1` · `get_video_likes` · `get_video_likes_alt1` · … | -| `client.live_analytics` | `LiveAnalytics` / `AsyncLiveAnalytics` | 1 | `export_vpaas_viewer_analytics` | -| `client.live_audio_tracks` | `LiveAudioTracks` / `AsyncLiveAudioTracks` | 2 | `get_event_audio_tracks` · `update_event_audio_tracks` | -| `client.live_embed_privacy` | `LiveEmbedPrivacy` / `AsyncLiveEmbedPrivacy` | 6 | `get_live_event_whitelist` · `get_live_event_whitelist_alt1` · `get_live_event_whitelist_alt2` · `set_live_event_whitelist` · `set_live_event_whitelist_alt1` · `set_live_event_whitelist_alt2` | -| `client.live_essentials` | `LiveEssentials` / `AsyncLiveEssentials` | 19 | `create_live_event` · `create_live_event_alt1` · `create_live_event_alt2` · `delete_live_event` · `delete_live_event_alt1` · `delete_live_event_alt2` · … | -| `client.live_event_m3_u8_playback` | `LiveEventM3U8Playback` / `AsyncLiveEventM3U8Playback` | 2 | `get_live_event_m3u8_playback` · `get_live_event_m3u8_playback_alt1` | -| `client.live_event_activation` | `LiveEventActivation` / `AsyncLiveEventActivation` | 3 | `activate_live_event` · `activate_live_event_alt1` · `activate_live_event_alt2` | -| `client.live_event_automated_closed_captions` | `LiveEventAutomatedClosedCaptions` / `AsyncLiveEventAutomatedClosedCaptions` | 3 | `edit_live_event_auto_cc` · `edit_live_event_auto_cc_alt1` · `edit_live_event_auto_cc_alt2` | -| `client.live_event_destinations` | `LiveEventDestinations` / `AsyncLiveEventDestinations` | 12 | `create_live_event_destination` · `create_live_event_destination_alt1` · `create_ott_destination` · `delete_live_event_destination` · `delete_ott_destination` · `get_available_destinations` · … | -| `client.live_event_end` | `LiveEventEnd` / `AsyncLiveEventEnd` | 3 | `end_live_event` · `end_live_event_alt1` · `end_live_event_alt2` | -| `client.live_event_low_latency` | `LiveEventLowLatency` / `AsyncLiveEventLowLatency` | 3 | `toggle_rle_low_latency` · `toggle_rle_low_latency_alt1` · `toggle_rle_low_latency_alt2` | -| `client.live_event_sessions` | `LiveEventSessions` / `AsyncLiveEventSessions` | 2 | `get_live_clip_ingest_status` · `get_live_ingest_status` | -| `client.live_event_thumbnails` | `LiveEventThumbnails` / `AsyncLiveEventThumbnails` | 15 | `create_live_event_thumbnail` · `create_live_event_thumbnail_alt1` · `create_live_event_thumbnail_alt2` · `delete_live_event_thumbnail` · `delete_live_event_thumbnail_alt1` · `delete_live_event_thumbnail_alt2` · … | -| `client.live_event_videos` | `LiveEventVideos` / `AsyncLiveEventVideos` | 12 | `add_videos_to_live_event` · `add_videos_to_live_event_alt1` · `add_videos_to_live_event_alt2` · `get_live_event_video` · `get_live_event_video_alt1` · `get_live_event_video_alt2` · … | -| `client.live_graphics` | `LiveGraphics` / `AsyncLiveGraphics` | 3 | `add_composer_video_graphic_item` · `delete_composer_video_graphic_item` · `get_composer_video_graphic_items` | -| `client.live_scenes` | `LiveScenes` / `AsyncLiveScenes` | 5 | `attach_graphic_item_to_scene` · `create_composer_scene` · `get_composer_scenes` · `reset_composer_scenes` · `update_composer_scene` | -| `client.on_demand_backgrounds` | `OnDemandBackgrounds` / `AsyncOnDemandBackgrounds` | 5 | `create_vod_background` · `delete_vod_background` · `edit_vod_background` · `get_vod_background` · `get_vod_backgrounds` | -| `client.on_demand_essentials` | `OnDemandEssentials` / `AsyncOnDemandEssentials` | 7 | `create_vod` · `create_vod_alt1` · `delete_vod_draft` · `edit_vod` · `get_user_vods` · `get_user_vods_alt1` · … | -| `client.on_demand_genres` | `OnDemandGenres` / `AsyncOnDemandGenres` | 8 | `add_vod_genre` · `delete_vod_genre` · `get_genre_vod` · `get_genre_vods` · `get_vod_genre` · `get_vod_genre_by_ondemand_id` · … | -| `client.on_demand_posters` | `OnDemandPosters` / `AsyncOnDemandPosters` | 4 | `add_vod_poster` · `edit_vod_poster` · `get_vod_poster` · `get_vod_posters` | -| `client.on_demand_promotions` | `OnDemandPromotions` / `AsyncOnDemandPromotions` | 5 | `create_vod_promotion` · `delete_vod_promotion` · `get_vod_promotion` · `get_vod_promotion_codes` · `get_vod_promotions` | -| `client.on_demand_purchases_and_rentals` | `OnDemandPurchasesAndRentals` / `AsyncOnDemandPurchasesAndRentals` | 3 | `check_if_vod_was_purchased_alt1` · `get_vod_purchases` · `get_vod_purchases_alt1` | -| `client.on_demand_regions` | `OnDemandRegions` / `AsyncOnDemandRegions` | 8 | `add_vod_region` · `delete_vod_region` · `delete_vod_regions` · `get_region` · `get_regions` · `get_vod_region` · … | -| `client.on_demand_seasons` | `OnDemandSeasons` / `AsyncOnDemandSeasons` | 3 | `get_vod_season` · `get_vod_season_videos` · `get_vod_seasons` | -| `client.on_demand_videos` | `OnDemandVideos` / `AsyncOnDemandVideos` | 4 | `add_video_to_vod` · `delete_video_from_vod` · `get_vod_video` · `get_vod_videos` | -| `client.payments_essentials` | `PaymentsEssentials` / `AsyncPaymentsEssentials` | 4 | `delete_payment_method` · `get_payment_method_info` · `get_subscription_info` · `list_payment_methods` | -| `client.portfolios_essentials` | `PortfoliosEssentials` / `AsyncPortfoliosEssentials` | 4 | `get_portfolio` · `get_portfolio_alt1` · `get_portfolios` · `get_portfolios_alt1` | -| `client.portfolios_videos` | `PortfoliosVideos` / `AsyncPortfoliosVideos` | 8 | `add_video_to_portfolio` · `add_video_to_portfolio_alt1` · `delete_video_from_portfolio` · `delete_video_from_portfolio_alt1` · `get_portfolio_video` · `get_portfolio_video_alt1` · … | -| `client.search_federated` | `SearchFederated` / `AsyncSearchFederated` | 2 | `federated_search_user_items` · `federated_search_user_items_alt1` | -| `client.showcases_custom_showcase_logos` | `ShowcasesCustomShowcaseLogos` / `AsyncShowcasesCustomShowcaseLogos` | 5 | `create_showcase_logo` · `delete_showcase_logo` · `get_showcase_logo` · `get_showcase_logos` · `replace_showcase_logo` | -| `client.showcases_custom_showcase_thumbnails` | `ShowcasesCustomShowcaseThumbnails` / `AsyncShowcasesCustomShowcaseThumbnails` | 5 | `create_showcase_custom_thumb` · `delete_showcase_custom_thumbnail` · `get_showcase_custom_thumbnail` · `get_showcase_custom_thumbs` · `replace_showcase_custom_thumb` | -| `client.showcases_essentials` | `ShowcasesEssentials` / `AsyncShowcasesEssentials` | 14 | `add_folder_to_showcases` · `copy_showcase` · `copy_showcase_alt2` · `create_showcase` · `create_showcase_alt1` · `delete_showcase` · … | -| `client.showcases_showcase_videos` | `ShowcasesShowcaseVideos` / `AsyncShowcasesShowcaseVideos` | 16 | `add_video_to_showcase` · `add_video_to_showcase_alt2` · `get_available_showcase_videos` · `get_available_video_showcases` · `get_showcase_video` · `get_showcase_video_alt2` · … | -| `client.subscription_plans_essentials` | `SubscriptionPlansEssentials` / `AsyncSubscriptionPlansEssentials` | 1 | `get_plan` | -| `client.tags_essentials` | `TagsEssentials` / `AsyncTagsEssentials` | 1 | `get_tag` | -| `client.teams_essentials` | `TeamsEssentials` / `AsyncTeamsEssentials` | 5 | `backfill_team_custom_metadata` · `get_team_custom_metadata_incomplete_videos` · `get_team_custom_metadata_settings` · `update_custom_metadata_field_options` · `upsert_custom_metadata_settings` | -| `client.teams_members` | `TeamsMembers` / `AsyncTeamsMembers` | 3 | `get_team_information` · `get_team_information2` · `get_team_role_information` | -| `client.tutorial_essentials` | `TutorialEssentials` / `AsyncTutorialEssentials` | 1 | `developer_tutorial` | -| `client.users_analytics` | `UsersAnalytics` / `AsyncUsersAnalytics` | 2 | `get_user_analytics` · `get_user_analytics_alt1` | -| `client.users_essentials` | `UsersEssentials` / `AsyncUsersEssentials` | 4 | `edit_user` · `edit_user_alt1` · `get_user` · `get_user_alt1` | -| `client.users_feeds` | `UsersFeeds` / `AsyncUsersFeeds` | 2 | `get_feed` · `get_feed_alt1` | -| `client.users_followers` | `UsersFollowers` / `AsyncUsersFollowers` | 12 | `check_if_user_is_following` · `check_if_user_is_following_alt1` · `follow_user` · `follow_user_alt1` · `follow_users` · `follow_users_alt1` · … | -| `client.users_lms` | `UsersLms` / `AsyncUsersLms` | 1 | `download_lms_package` | -| `client.users_pictures` | `UsersPictures` / `AsyncUsersPictures` | 10 | `create_picture` · `create_picture_alt1` · `delete_picture` · `delete_picture_alt1` · `edit_picture` · `edit_picture_alt1` · … | -| `client.users_search` | `UsersSearch` / `AsyncUsersSearch` | 1 | `search_users` | -| `client.users_watch_history` | `UsersWatchHistory` / `AsyncUsersWatchHistory` | 3 | `delete_from_watch_history` · `delete_watch_history` · `get_watch_history` | -| `client.videos_ai` | `VideosAi` / `AsyncVideosAi` | 11 | `ask_ai_question` · `edit_ai_texttrack` · `get_ai_languages` · `get_ai_metadata` · `get_ai_questions` · `get_ai_transcribe_status` · … | -| `client.videos_animated_thumbnails` | `VideosAnimatedThumbnails` / `AsyncVideosAnimatedThumbnails` | 5 | `create_animated_thumbset` · `delete_animated_thumbset` · `get_all_animated_thumbset` · `get_animated_thumbset` · `get_animated_thumbset_status` | -| `client.videos_chapters` | `VideosChapters` / `AsyncVideosChapters` | 12 | `create_chapter` · `create_chapter_thumbnail_or_upload_link` · `create_unsaved_chapter_thumbnail_or_upload_link` · `delete_chapter` · `delete_chapter_thumbnail` · `edit_chapter` · … | -| `client.videos_content_ratings` | `VideosContentRatings` / `AsyncVideosContentRatings` | 1 | `get_content_ratings` | -| `client.videos_creative_commons` | `VideosCreativeCommons` / `AsyncVideosCreativeCommons` | 1 | `get_cc_licenses` | -| `client.videos_credits` | `VideosCredits` / `AsyncVideosCredits` | 8 | `add_video_credit` · `add_video_credit_alt1` · `available_users` · `delete_video_credit` · `edit_video_credit` · `get_video_credit` · … | -| `client.videos_embed_privacy` | `VideosEmbedPrivacy` / `AsyncVideosEmbedPrivacy` | 3 | `add_video_privacy_domain` · `delete_video_privacy_domain` · `get_video_privacy_domains` | -| `client.videos_essentials` | `VideosEssentials` / `AsyncVideosEssentials` | 15 | `check_if_user_owns_video` · `check_if_user_owns_video_alt1` · `copy_video` · `copy_video_alt2` · `delete_video` · `delete_videos` · … | -| `client.videos_fragments` | `VideosFragments` / `AsyncVideosFragments` | 3 | `delete_fragments` · `get_fragments` · `put_fragments` | -| `client.videos_languages` | `VideosLanguages` / `AsyncVideosLanguages` | 1 | `get_languages` | -| `client.videos_live_m3_u8_playback` | `VideosLiveM3U8Playback` / `AsyncVideosLiveM3U8Playback` | 2 | `get_one_time_event_m3u8_playback` · `get_one_time_event_m3u8_playback_alt1` | -| `client.videos_moderation` | `VideosModeration` / `AsyncVideosModeration` | 1 | `get_video_reporting_reasons` | -| `client.videos_nondestructive_trimming` | `VideosNondestructiveTrimming` / `AsyncVideosNondestructiveTrimming` | 1 | `clip_trim` | -| `client.videos_recommendations` | `VideosRecommendations` / `AsyncVideosRecommendations` | 1 | `get_related_videos` | -| `client.videos_showcases` | `VideosShowcases` / `AsyncVideosShowcases` | 2 | `add_or_remove_multiple_albums` · `get_video_albums` | -| `client.videos_tags` | `VideosTags` / `AsyncVideosTags` | 6 | `add_video_tag` · `add_video_tags` · `check_video_for_tag` · `delete_video_tag` · `get_video_tags` · `get_videos_with_tag` | -| `client.videos_text_tracks` | `VideosTextTracks` / `AsyncVideosTextTracks` | 9 | `create_text_track` · `create_text_track_alt1` · `delete_text_track` · `edit_text_track` · `get_text_track` · `get_text_tracks` · … | -| `client.videos_thumbnails` | `VideosThumbnails` / `AsyncVideosThumbnails` | 7 | `create_video_thumbnail` · `create_video_thumbnail_alt1` · `delete_video_thumbnail` · `edit_video_thumbnail` · `get_video_thumbnail` · `get_video_thumbnails` · … | -| `client.videos_transcripts` | `VideosTranscripts` / `AsyncVideosTranscripts` | 2 | `get_transcript` · `get_transcript_metadata` | -| `client.videos_unlisted_videos` | `VideosUnlistedVideos` / `AsyncVideosUnlistedVideos` | 6 | `add_video_privacy_user` · `add_video_privacy_users` · `add_video_privacy_users_alt1` · `delete_video_privacy_user` · `get_video_privacy_users` · `get_video_privacy_users_alt1` | -| `client.videos_uploads` | `VideosUploads` / `AsyncVideosUploads` | 4 | `complete_streaming_upload` · `get_upload_attempt` · `upload_video` · `upload_video_alt1` | -| `client.videos_versions` | `VideosVersions` / `AsyncVideosVersions` | 13 | `create_audio_track` · `create_video_version` · `delete_audio_track` · `delete_video_version` · `edit_audio_track` · `edit_video_version` · … | -| `client.videos_video_comments` | `VideosVideoComments` / `AsyncVideosVideoComments` | 9 | `create_comment` · `create_comment_alt1` · `create_comment_reply` · `delete_comment` · `edit_comment` · `get_comment` · … | -| `client.watch_later_queue_essentials` | `WatchLaterQueueEssentials` / `AsyncWatchLaterQueueEssentials` | 8 | `add_video_to_watch_later` · `add_video_to_watch_later_alt1` · `check_watch_later_queue` · `check_watch_later_queue_alt1` · `delete_video_from_watch_later` · `delete_video_from_watch_later_alt1` · … | - -Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. - -The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. - -## SDK map — look up first, open the module second - -The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `vimeo_api/` source directory, and these two entries: - -- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. -- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. - -**Installing the distribution does not give you the map.** `pyproject.toml` ships the `vimeo_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: - -```bash -git clone --depth 1 --branch main https://github.com/context-plugins/vimeo-python-sdk -``` - -Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. - -Every `Source` path on the map is relative to that SDK root, so `vimeo_api/models/api_app.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. - -**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* - -**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `vimeo_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. - -`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. - -## Contract facts — the map first, then the module - -**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. - -Read the one module that owns the fact **inside the installed package**. Locate it first: - -```bash -python -c "import vimeo_api, pathlib; print(pathlib.Path(vimeo_api.__file__).parent)" -``` - -Failing that, it is under the project's environment (`.venv/Lib/site-packages/vimeo_api` on Windows, `.venv/lib/python3.*/site-packages/vimeo_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: - -| Question | Module | -| --- | --- | -| An operation's real signature, parameters and return type | `apis/.py` | -| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | -| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | -| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | -| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | -| `Success`/`Failure`/`RawError` | `core/results.py` | -| Per-call overrides | `core/request_options.py` | -| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | -| Model base config, `to_dict`/`to_json` | `core/models.py` | -| A model's members, required vs `UNSET`, wire aliases | `models/.py` | -| An enum's members and wire values | `models/enums/` | -| Open-enum coercion | `core/converters/open_enum.py` | -| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | -| Transport protocols (the test seam) | `core/transport.py` | -| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | -| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | -| Base-URL resolution | `server/server_config.py`, `server/server.py` | -| An operation's error mapper (status → schema) | `errors/_error.py` | - -**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. - -Keep lookups cheap — the rules that keep a session's context small: - -- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. -- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. -- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. - -## Integration workflow — load the companion skill at each step - -Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: - -1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) -2. **Authentication** — load **python-authentication** before you set credentials. The three schemes are `bearer=`, `oauth2_authorization_code=` and `oauth2_client_credentials=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) -3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **174 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) -4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`/`…OrInt`), so an unknown wire value passes through as a plain `str`/`int` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) -5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) -6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) -7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) - -## What a contract sheet must carry for this SDK - -Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: - -1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. -2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. -3. **The 174 operations that return `None`** — `api_apps_webhooks.delete_webhook` · `api_information_essentials.get_endpoints` · `authentication_extras_essentials.delete_token` · `categories_users.check_if_user_subscribed_to_category` · `categories_users.check_if_user_subscribed_to_category_alt1` · `categories_users.subscribe_to_category` · `categories_users.subscribe_to_category_alt1` · `categories_users.unsubscribe_from_category` · `categories_users.unsubscribe_from_category_alt1` · `channels_categories.add_channel_categories` · `channels_categories.categorize_channel` · `channels_categories.delete_channel_category` · `channels_essentials.delete_channel` · `channels_moderators.add_channel_moderator` · `channels_moderators.add_channel_moderators` · `channels_moderators.remove_channel_moderator` · `channels_moderators.remove_channel_moderators` · `channels_private_channel_members.delete_channel_privacy_user` · `channels_private_channel_members.set_channel_privacy_user` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel_alt1` · `channels_subscriptions_and_subscribers.subscribe_to_channel` · `channels_subscriptions_and_subscribers.subscribe_to_channel_alt1` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel_alt1` · `channels_tags.add_channel_tag` · `channels_tags.check_if_channel_has_tag` · `channels_tags.delete_tag_from_channel` · `channels_videos.add_video_to_channel` · `channels_videos.add_videos_to_channel` · `channels_videos.delete_video_from_channel` · `channels_videos.remove_videos_from_channel` · `embed_presets_custom_logos.delete_custom_logo` · `embed_presets_custom_logos.delete_custom_logo_alt1` · `embed_presets_essentials.delete_embed_preset` · `embed_presets_essentials.delete_embed_preset_alt1` · `embed_presets_folders.delete_folder_embed_preset` · `embed_presets_videos.add_video_embed_preset` · `embed_presets_videos.delete_video_embed_preset` · `embed_presets_videos.get_video_embed_preset` · `folders_essentials.delete_project` · `folders_essentials.delete_project_alt1` · `folders_items.delete_folder_items_alt1` · `folders_items.get_folder_items` · `folders_items.get_folder_items2` · `folders_videos.add_video_to_project` · `folders_videos.add_video_to_project_alt1` · `folders_videos.add_videos_to_project` · `folders_videos.add_videos_to_project_alt1` · `folders_videos.remove_video_from_project` · `folders_videos.remove_video_from_project_alt1` · `folders_videos.remove_videos_from_project` · `folders_videos.remove_videos_from_project_alt1` · `groups_essentials.delete_group` · `groups_subscriptions.join_group` · `groups_subscriptions.join_group_alt1` · `groups_subscriptions.leave_group` · `groups_subscriptions.leave_group_alt1` · `groups_users.check_if_user_joined_group` · `groups_users.check_if_user_joined_group_alt1` · `groups_videos.delete_video_from_group` · `likes_essentials.check_if_user_liked_video` · `likes_essentials.check_if_user_liked_video_alt1` · `likes_essentials.like_video` · `likes_essentials.like_video_alt1` · `likes_essentials.unlike_video` · `likes_essentials.unlike_video_alt1` · `live_analytics.export_vpaas_viewer_analytics` · `live_embed_privacy.set_live_event_whitelist` · `live_embed_privacy.set_live_event_whitelist_alt1` · `live_embed_privacy.set_live_event_whitelist_alt2` · `live_essentials.delete_live_event` · `live_essentials.delete_live_event_alt1` · `live_essentials.delete_live_event_alt2` · `live_essentials.delete_live_events` · `live_essentials.delete_live_events_alt1` · `live_essentials.delete_live_events_alt2` · `live_event_m3_u8_playback.get_live_event_m3u8_playback` · `live_event_m3_u8_playback.get_live_event_m3u8_playback_alt1` · `live_event_destinations.delete_live_event_destination` · `live_event_destinations.delete_ott_destination` · `live_event_destinations.get_available_destinations` · `live_event_destinations.get_available_destinations_alt1` · `live_event_thumbnails.delete_live_event_thumbnail` · `live_event_thumbnails.delete_live_event_thumbnail_alt1` · `live_event_thumbnails.delete_live_event_thumbnail_alt2` · `live_event_videos.add_videos_to_live_event` · `live_event_videos.add_videos_to_live_event_alt1` · `live_event_videos.add_videos_to_live_event_alt2` · `live_event_videos.remove_videos_from_live_event` · `live_event_videos.remove_videos_from_live_event_alt1` · `live_event_videos.remove_videos_from_live_event_alt2` · `live_graphics.delete_composer_video_graphic_item` · `on_demand_backgrounds.delete_vod_background` · `on_demand_essentials.delete_vod_draft` · `on_demand_genres.delete_vod_genre` · `on_demand_promotions.delete_vod_promotion` · `on_demand_regions.delete_vod_region` · `on_demand_videos.delete_video_from_vod` · `payments_essentials.delete_payment_method` · `payments_essentials.get_subscription_info` · `portfolios_videos.add_video_to_portfolio` · `portfolios_videos.add_video_to_portfolio_alt1` · `portfolios_videos.delete_video_from_portfolio` · `portfolios_videos.delete_video_from_portfolio_alt1` · `showcases_custom_showcase_logos.delete_showcase_logo` · `showcases_custom_showcase_thumbnails.delete_showcase_custom_thumbnail` · `showcases_essentials.add_folder_to_showcases` · `showcases_essentials.delete_showcase` · `showcases_essentials.delete_showcase_alt2` · `showcases_essentials.update_showcases` · `showcases_showcase_videos.add_video_to_showcase` · `showcases_showcase_videos.add_video_to_showcase_alt2` · `showcases_showcase_videos.remove_video_from_showcase` · `showcases_showcase_videos.remove_video_from_showcase_alt2` · `showcases_showcase_videos.replace_videos_in_showcase` · `showcases_showcase_videos.replace_videos_in_showcase_alt2` · `subscription_plans_essentials.get_plan` · `teams_essentials.backfill_team_custom_metadata` · `teams_essentials.get_team_custom_metadata_incomplete_videos` · `teams_members.get_team_information` · `teams_members.get_team_information2` · `teams_members.get_team_role_information` · `tutorial_essentials.developer_tutorial` · `users_followers.check_if_user_is_following` · `users_followers.check_if_user_is_following_alt1` · `users_followers.follow_user` · `users_followers.follow_user_alt1` · `users_followers.follow_users` · `users_followers.follow_users_alt1` · `users_followers.unfollow_user` · `users_followers.unfollow_user_alt1` · `users_lms.download_lms_package` · `users_pictures.delete_picture` · `users_pictures.delete_picture_alt1` · `users_watch_history.delete_from_watch_history` · `users_watch_history.delete_watch_history` · `videos_ai.ask_ai_question` · `videos_ai.edit_ai_texttrack` · `videos_ai.get_ai_languages` · `videos_ai.get_ai_metadata` · `videos_ai.get_ai_questions` · `videos_ai.get_ai_transcribe_status` · `videos_ai.get_ai_translate_dubbing_status` · `videos_ai.get_ai_translate_subtitles_status` · `videos_ai.start_ai_transcribe` · `videos_ai.start_ai_translate_dubbing` · `videos_ai.start_ai_translate_subtitles` · `videos_animated_thumbnails.delete_animated_thumbset` · `videos_chapters.delete_chapter` · `videos_chapters.delete_chapter_thumbnail` · `videos_credits.delete_video_credit` · `videos_embed_privacy.add_video_privacy_domain` · `videos_embed_privacy.delete_video_privacy_domain` · `videos_essentials.delete_video` · `videos_essentials.delete_videos` · `videos_essentials.delete_videos_alt1` · `videos_fragments.delete_fragments` · `videos_fragments.put_fragments` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback_alt1` · `videos_tags.delete_video_tag` · `videos_text_tracks.delete_text_track` · `videos_thumbnails.delete_video_thumbnail` · `videos_unlisted_videos.delete_video_privacy_user` · `videos_uploads.complete_streaming_upload` · `videos_versions.delete_audio_track` · `videos_versions.delete_video_version` · `videos_versions.get_audiotrack_downloads` · `videos_video_comments.delete_comment` · `watch_later_queue_essentials.add_video_to_watch_later` · `watch_later_queue_essentials.add_video_to_watch_later_alt1` · `watch_later_queue_essentials.delete_video_from_watch_later` · `watch_later_queue_essentials.delete_video_from_watch_later_alt1`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. -4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. -5. **The `ApiError.error` union** for each operation in scope — there are **three** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: - 1. `LegacyError` — 237 operations (`api_apps_webhooks.delete_webhook` · `authentication_extras_essentials.delete_token` · `authentication_extras_essentials.verify_token` · `categories_channels.get_category_channels` · `categories_essentials.get_category` · `categories_groups.get_category_groups` · `categories_videos.check_category_for_video` · `categories_videos.get_category_videos` · `categories_videos.get_video_categories` · `categories_videos.suggest_video_category` · `channels_categories.add_channel_categories` · `channels_categories.categorize_channel` · `channels_categories.delete_channel_category` · `channels_categories.get_channel_categories` · `channels_essentials.create_channel` · `channels_essentials.delete_channel` · `channels_essentials.edit_channel` · `channels_essentials.get_channels` · `channels_moderators.add_channel_moderator` · `channels_moderators.add_channel_moderators` · `channels_moderators.remove_channel_moderator` · `channels_moderators.remove_channel_moderators` · `channels_moderators.replace_channel_moderators` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel_alt1` · `channels_subscriptions_and_subscribers.get_channel_subscribers` · `channels_subscriptions_and_subscribers.subscribe_to_channel` · `channels_subscriptions_and_subscribers.subscribe_to_channel_alt1` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel_alt1` · `channels_tags.add_channel_tag` · `channels_tags.add_tags_to_channel` · `channels_tags.check_if_channel_has_tag` · `channels_tags.delete_tag_from_channel` · `channels_tags.get_channel_tags` · `channels_videos.add_video_to_channel` · `channels_videos.add_videos_to_channel` · `channels_videos.delete_video_from_channel` · `channels_videos.get_available_video_channels` · `channels_videos.get_channel_video` · `channels_videos.get_channel_videos` · `channels_videos.remove_videos_from_channel` · `embed_presets_custom_logos.create_custom_logo` · `embed_presets_custom_logos.create_custom_logo_alt1` · `embed_presets_custom_logos.delete_custom_logo` · `embed_presets_custom_logos.delete_custom_logo_alt1` · `embed_presets_custom_logos.get_custom_logo` · `embed_presets_custom_logos.get_custom_logo_alt1` · `embed_presets_custom_logos.get_custom_logos` · `embed_presets_custom_logos.get_custom_logos_alt1` · `embed_presets_essentials.delete_embed_preset` · `embed_presets_essentials.delete_embed_preset_alt1` · `embed_presets_essentials.edit_embed_preset` · `embed_presets_essentials.edit_embed_preset_alt1` · `embed_presets_folders.delete_folder_embed_preset` · `embed_presets_timeline_events.create_video_custom_logo` · `embed_presets_timeline_events.get_video_custom_logo` · `embed_presets_videos.delete_video_embed_preset` · `embed_presets_videos.get_video_embed_preset` · `groups_essentials.create_group` · `groups_essentials.delete_group` · `groups_subscriptions.join_group` · `groups_subscriptions.join_group_alt1` · `groups_subscriptions.leave_group` · `groups_subscriptions.leave_group_alt1` · `groups_users.check_if_user_joined_group` · `groups_users.check_if_user_joined_group_alt1` · `groups_users.get_group_members` · `groups_videos.add_video_to_group` · `groups_videos.delete_video_from_group` · `groups_videos.get_group_video` · `groups_videos.get_group_videos` · `likes_essentials.check_if_user_liked_video` · `likes_essentials.check_if_user_liked_video_alt1` · `likes_essentials.like_video` · `likes_essentials.like_video_alt1` · `likes_essentials.unlike_video` · `likes_essentials.unlike_video_alt1` · `on_demand_backgrounds.create_vod_background` · `on_demand_backgrounds.delete_vod_background` · `on_demand_backgrounds.edit_vod_background` · `on_demand_backgrounds.get_vod_background` · `on_demand_backgrounds.get_vod_backgrounds` · `on_demand_essentials.delete_vod_draft` · `on_demand_essentials.edit_vod` · `on_demand_essentials.get_user_vods` · `on_demand_essentials.get_user_vods_alt1` · `on_demand_essentials.get_vod` · `on_demand_genres.add_vod_genre` · `on_demand_genres.delete_vod_genre` · `on_demand_genres.get_genre_vod` · `on_demand_genres.get_vod_genre` · `on_demand_genres.get_vod_genre_by_ondemand_id` · `on_demand_genres.get_vod_genres_by_ondemand_id` · `on_demand_posters.add_vod_poster` · `on_demand_posters.get_vod_posters` · `on_demand_promotions.create_vod_promotion` · `on_demand_promotions.delete_vod_promotion` · `on_demand_promotions.get_vod_promotion` · `on_demand_promotions.get_vod_promotion_codes` · `on_demand_promotions.get_vod_promotions` · `on_demand_purchases_and_rentals.check_if_vod_was_purchased_alt1` · `on_demand_purchases_and_rentals.get_vod_purchases` · `on_demand_purchases_and_rentals.get_vod_purchases_alt1` · `on_demand_regions.add_vod_region` · `on_demand_regions.delete_vod_region` · `on_demand_regions.delete_vod_regions` · `on_demand_regions.get_region` · `on_demand_regions.get_vod_region` · `on_demand_regions.get_vod_regions` · `on_demand_regions.set_vod_regions` · `on_demand_seasons.get_vod_season` · `on_demand_seasons.get_vod_seasons` · `on_demand_videos.add_video_to_vod` · `on_demand_videos.delete_video_from_vod` · `portfolios_videos.add_video_to_portfolio` · `portfolios_videos.add_video_to_portfolio_alt1` · `portfolios_videos.delete_video_from_portfolio` · `portfolios_videos.delete_video_from_portfolio_alt1` · `showcases_custom_showcase_logos.create_showcase_logo` · `showcases_custom_showcase_logos.delete_showcase_logo` · `showcases_custom_showcase_logos.get_showcase_logo` · `showcases_custom_showcase_logos.get_showcase_logos` · `showcases_custom_showcase_logos.replace_showcase_logo` · `showcases_custom_showcase_thumbnails.create_showcase_custom_thumb` · `showcases_custom_showcase_thumbnails.delete_showcase_custom_thumbnail` · `showcases_custom_showcase_thumbnails.get_showcase_custom_thumbnail` · `showcases_custom_showcase_thumbnails.get_showcase_custom_thumbs` · `showcases_custom_showcase_thumbnails.replace_showcase_custom_thumb` · `showcases_essentials.copy_showcase` · `showcases_essentials.copy_showcase_alt2` · `showcases_essentials.create_showcase` · `showcases_essentials.create_showcase_alt1` · `showcases_essentials.delete_showcase` · `showcases_essentials.delete_showcase_alt2` · `showcases_essentials.edit_showcase` · `showcases_essentials.edit_showcase_alt2` · `showcases_essentials.get_showcase` · `showcases_essentials.get_showcase_alt2` · `showcases_essentials.get_showcases` · `showcases_essentials.get_showcases_alt1` · `showcases_showcase_videos.add_video_to_showcase` · `showcases_showcase_videos.add_video_to_showcase_alt2` · `showcases_showcase_videos.get_available_showcase_videos` · `showcases_showcase_videos.get_showcase_video` · `showcases_showcase_videos.get_showcase_video_alt2` · `showcases_showcase_videos.get_showcase_videos` · `showcases_showcase_videos.get_showcase_videos_alt2` · `showcases_showcase_videos.remove_video_from_showcase` · `showcases_showcase_videos.remove_video_from_showcase_alt2` · `showcases_showcase_videos.replace_videos_in_showcase` · `showcases_showcase_videos.replace_videos_in_showcase_alt2` · `teams_essentials.backfill_team_custom_metadata` · `teams_essentials.get_team_custom_metadata_incomplete_videos` · `teams_essentials.get_team_custom_metadata_settings` · `teams_essentials.update_custom_metadata_field_options` · `teams_essentials.upsert_custom_metadata_settings` · `users_followers.check_if_user_is_following` · `users_followers.check_if_user_is_following_alt1` · `users_followers.follow_user` · `users_followers.follow_user_alt1` · `users_search.search_users` · `users_watch_history.get_watch_history` · `videos_ai.ask_ai_question` · `videos_ai.edit_ai_texttrack` · `videos_ai.get_ai_metadata` · `videos_ai.get_ai_questions` · `videos_ai.get_ai_transcribe_status` · `videos_ai.get_ai_translate_dubbing_status` · `videos_ai.get_ai_translate_subtitles_status` · `videos_ai.start_ai_transcribe` · `videos_ai.start_ai_translate_dubbing` · `videos_ai.start_ai_translate_subtitles` · `videos_animated_thumbnails.create_animated_thumbset` · `videos_chapters.create_chapter` · `videos_chapters.create_chapter_thumbnail_or_upload_link` · `videos_chapters.create_unsaved_chapter_thumbnail_or_upload_link` · `videos_chapters.delete_chapter` · `videos_chapters.delete_chapter_thumbnail` · `videos_chapters.edit_chapter` · `videos_chapters.get_chapter` · `videos_chapters.get_chapter_thumbnail` · `videos_chapters.get_chapter_thumbnails` · `videos_chapters.get_chapters` · `videos_chapters.get_unsaved_chapter_thumbnail` · `videos_chapters.set_chapter_thumbnail_active` · `videos_credits.add_video_credit` · `videos_credits.add_video_credit_alt1` · `videos_credits.delete_video_credit` · `videos_credits.edit_video_credit` · `videos_credits.get_video_credit` · `videos_embed_privacy.add_video_privacy_domain` · `videos_embed_privacy.delete_video_privacy_domain` · `videos_embed_privacy.get_video_privacy_domains` · `videos_essentials.check_if_user_owns_video` · `videos_essentials.check_if_user_owns_video_alt1` · `videos_essentials.copy_video` · `videos_essentials.copy_video_alt2` · `videos_essentials.delete_video` · `videos_essentials.edit_video` · `videos_essentials.get_video` · `videos_essentials.search_videos` · `videos_essentials.update_video_custom_metadata` · `videos_recommendations.get_related_videos` · `videos_tags.add_video_tag` · `videos_tags.add_video_tags` · `videos_tags.check_video_for_tag` · `videos_tags.delete_video_tag` · `videos_tags.get_videos_with_tag` · `videos_text_tracks.create_text_track` · `videos_text_tracks.create_text_track_alt1` · `videos_text_tracks.delete_text_track` · `videos_text_tracks.edit_text_track` · `videos_text_tracks.get_text_track` · `videos_text_tracks.get_text_tracks` · `videos_text_tracks.get_text_tracks_alt1` · `videos_text_tracks.get_text_tracks_alt2` · `videos_text_tracks.get_text_tracks_alt3` · `videos_unlisted_videos.add_video_privacy_user` · `videos_unlisted_videos.delete_video_privacy_user` · `videos_unlisted_videos.get_video_privacy_users` · `videos_unlisted_videos.get_video_privacy_users_alt1` · `videos_versions.create_audio_track` · `videos_versions.delete_audio_track` · `videos_versions.delete_video_version` · `videos_versions.edit_audio_track` · `videos_versions.edit_video_version` · `videos_versions.get_audio_track` · `videos_versions.get_audio_tracks` · `videos_versions.get_video_version` · `videos_versions.get_video_versions` · `videos_versions.get_video_versions_alt1` · `videos_video_comments.delete_comment` · `videos_video_comments.get_comment` · `videos_video_comments.get_comment_replies` · `watch_later_queue_essentials.check_watch_later_queue` · `watch_later_queue_essentials.check_watch_later_queue_alt1`); distinguishing members `error` - 2. `Error` — 218 operations (`api_apps_webhooks.add_webhook` · `api_apps_webhooks.get_webhook` · `api_apps_webhooks.update_webhook` · `authentication_extras_authenticate.client_auth` · `categories_users.get_category_subscriptions` · `categories_users.get_category_subscriptions_alt1` · `channels_categories.add_channel_categories` · `channels_categories.categorize_channel` · `channels_categories.delete_channel_category` · `channels_essentials.get_channel_subscriptions` · `channels_essentials.get_channel_subscriptions_alt1` · `channels_essentials.get_channels` · `channels_moderators.add_channel_moderators` · `channels_moderators.replace_channel_moderators` · `channels_private_channel_members.delete_channel_privacy_user` · `channels_private_channel_members.get_channel_privacy_users` · `channels_private_channel_members.set_channel_privacy_user` · `channels_private_channel_members.set_channel_privacy_users` · `channels_tags.add_channel_tag` · `channels_tags.add_tags_to_channel` · `channels_tags.check_if_channel_has_tag` · `channels_tags.delete_tag_from_channel` · `folders_essentials.create_project` · `folders_essentials.create_project_alt1` · `folders_essentials.delete_project` · `folders_essentials.delete_project_alt1` · `folders_essentials.edit_project` · `folders_essentials.edit_project_alt1` · `folders_essentials.get_private_to_me_folder` · `folders_essentials.get_project` · `folders_essentials.get_project_alt1` · `folders_essentials.get_projects` · `folders_essentials.get_projects_alt1` · `folders_items.delete_folder_items_alt1` · `folders_videos.add_video_to_project` · `folders_videos.add_video_to_project_alt1` · `folders_videos.add_videos_to_project` · `folders_videos.add_videos_to_project_alt1` · `folders_videos.get_project_videos` · `folders_videos.get_project_videos_alt1` · `folders_videos.remove_video_from_project` · `folders_videos.remove_video_from_project_alt1` · `folders_videos.remove_videos_from_project` · `folders_videos.remove_videos_from_project_alt1` · `groups_essentials.create_group` · `groups_essentials.delete_group` · `groups_essentials.get_group` · `groups_essentials.get_groups` · `groups_subscriptions.join_group` · `groups_subscriptions.join_group_alt1` · `groups_subscriptions.leave_group` · `groups_subscriptions.leave_group_alt1` · `groups_users.get_user_groups` · `groups_users.get_user_groups_alt1` · `groups_videos.add_video_to_group` · `groups_videos.delete_video_from_group` · `groups_videos.get_available_video_groups` · `likes_essentials.get_likes` · `likes_essentials.get_likes_alt1` · `likes_essentials.get_vod_likes` · `live_analytics.export_vpaas_viewer_analytics` · `live_audio_tracks.get_event_audio_tracks` · `live_audio_tracks.update_event_audio_tracks` · `live_embed_privacy.get_live_event_whitelist` · `live_embed_privacy.get_live_event_whitelist_alt1` · `live_embed_privacy.get_live_event_whitelist_alt2` · `live_embed_privacy.set_live_event_whitelist` · `live_embed_privacy.set_live_event_whitelist_alt1` · `live_embed_privacy.set_live_event_whitelist_alt2` · `live_essentials.create_live_event` · `live_essentials.create_live_event_alt1` · `live_essentials.create_live_event_alt2` · `live_essentials.delete_live_event` · `live_essentials.delete_live_event_alt1` · `live_essentials.delete_live_event_alt2` · `live_essentials.delete_live_events` · `live_essentials.delete_live_events_alt1` · `live_essentials.delete_live_events_alt2` · `live_essentials.get_live_event` · `live_essentials.get_live_event_alt1` · `live_essentials.get_live_event_alt2` · `live_essentials.update_live_event` · `live_essentials.update_live_event_alt1` · `live_essentials.update_live_event_alt2` · `live_event_m3_u8_playback.get_live_event_m3u8_playback` · `live_event_m3_u8_playback.get_live_event_m3u8_playback_alt1` · `live_event_activation.activate_live_event` · `live_event_activation.activate_live_event_alt1` · `live_event_activation.activate_live_event_alt2` · `live_event_automated_closed_captions.edit_live_event_auto_cc` · `live_event_automated_closed_captions.edit_live_event_auto_cc_alt1` · `live_event_automated_closed_captions.edit_live_event_auto_cc_alt2` · `live_event_destinations.create_live_event_destination` · `live_event_destinations.create_live_event_destination_alt1` · `live_event_destinations.create_ott_destination` · `live_event_destinations.delete_live_event_destination` · `live_event_destinations.delete_ott_destination` · `live_event_destinations.get_available_destinations` · `live_event_destinations.get_available_destinations_alt1` · `live_event_destinations.get_live_event_destination` · `live_event_destinations.get_live_event_destinations` · `live_event_destinations.get_live_event_destinations_alt1` · `live_event_destinations.get_ott_destinations` · `live_event_destinations.update_live_event_destination` · `live_event_end.end_live_event` · `live_event_end.end_live_event_alt1` · `live_event_end.end_live_event_alt2` · `live_event_low_latency.toggle_rle_low_latency` · `live_event_low_latency.toggle_rle_low_latency_alt1` · `live_event_low_latency.toggle_rle_low_latency_alt2` · `live_event_thumbnails.create_live_event_thumbnail` · `live_event_thumbnails.create_live_event_thumbnail_alt1` · `live_event_thumbnails.create_live_event_thumbnail_alt2` · `live_event_thumbnails.delete_live_event_thumbnail` · `live_event_thumbnails.delete_live_event_thumbnail_alt1` · `live_event_thumbnails.delete_live_event_thumbnail_alt2` · `live_event_thumbnails.edit_live_event_thumbnail` · `live_event_thumbnails.edit_live_event_thumbnail_alt1` · `live_event_thumbnails.edit_live_event_thumbnail_alt2` · `live_event_thumbnails.get_live_event_thumbnail` · `live_event_thumbnails.get_live_event_thumbnail_alt1` · `live_event_thumbnails.get_live_event_thumbnail_alt2` · `live_event_thumbnails.get_live_event_thumbnails` · `live_event_thumbnails.get_live_event_thumbnails_alt1` · `live_event_thumbnails.get_live_event_thumbnails_alt2` · `live_event_videos.add_videos_to_live_event` · `live_event_videos.add_videos_to_live_event_alt1` · `live_event_videos.add_videos_to_live_event_alt2` · `live_event_videos.get_live_event_video` · `live_event_videos.get_live_event_video_alt1` · `live_event_videos.get_live_event_video_alt2` · `live_event_videos.get_live_event_videos` · `live_event_videos.get_live_event_videos_alt1` · `live_event_videos.get_live_event_videos_alt2` · `live_event_videos.remove_videos_from_live_event` · `live_event_videos.remove_videos_from_live_event_alt1` · `live_event_videos.remove_videos_from_live_event_alt2` · `live_graphics.add_composer_video_graphic_item` · `live_graphics.delete_composer_video_graphic_item` · `live_graphics.get_composer_video_graphic_items` · `live_scenes.attach_graphic_item_to_scene` · `live_scenes.create_composer_scene` · `live_scenes.get_composer_scenes` · `live_scenes.reset_composer_scenes` · `live_scenes.update_composer_scene` · `on_demand_genres.get_genre_vods` · `on_demand_genres.get_vod_genres` · `payments_essentials.delete_payment_method` · `payments_essentials.get_payment_method_info` · `payments_essentials.list_payment_methods` · `search_federated.federated_search_user_items` · `search_federated.federated_search_user_items_alt1` · `showcases_custom_showcase_thumbnails.get_showcase_custom_thumbs` · `showcases_essentials.add_folder_to_showcases` · `showcases_showcase_videos.get_available_showcase_videos` · `showcases_showcase_videos.get_available_video_showcases` · `showcases_showcase_videos.set_video_as_showcase_featured` · `showcases_showcase_videos.set_video_as_showcase_featured_alt2` · `showcases_showcase_videos.set_video_as_showcase_thumbnail` · `showcases_showcase_videos.set_video_as_showcase_thumbnail_alt2` · `teams_essentials.backfill_team_custom_metadata` · `teams_essentials.get_team_custom_metadata_incomplete_videos` · `teams_essentials.get_team_custom_metadata_settings` · `teams_essentials.update_custom_metadata_field_options` · `teams_essentials.upsert_custom_metadata_settings` · `users_essentials.edit_user` · `users_essentials.edit_user_alt1` · `users_feeds.get_feed` · `users_feeds.get_feed_alt1` · `users_followers.follow_users` · `users_followers.follow_users_alt1` · `users_search.search_users` · `videos_ai.ask_ai_question` · `videos_ai.edit_ai_texttrack` · `videos_ai.get_ai_languages` · `videos_ai.get_ai_metadata` · `videos_ai.get_ai_questions` · `videos_ai.get_ai_transcribe_status` · `videos_ai.get_ai_translate_dubbing_status` · `videos_ai.get_ai_translate_subtitles_status` · `videos_ai.start_ai_transcribe` · `videos_ai.start_ai_translate_dubbing` · `videos_ai.start_ai_translate_subtitles` · `videos_animated_thumbnails.create_animated_thumbset` · `videos_animated_thumbnails.delete_animated_thumbset` · `videos_animated_thumbnails.get_all_animated_thumbset` · `videos_animated_thumbnails.get_animated_thumbset` · `videos_animated_thumbnails.get_animated_thumbset_status` · `videos_chapters.edit_chapter` · `videos_essentials.delete_videos` · `videos_essentials.delete_videos_alt1` · `videos_essentials.get_appearances` · `videos_essentials.get_appearances_alt1` · `videos_essentials.search_videos` · `videos_essentials.update_video_custom_metadata` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback_alt1` · `videos_moderation.get_video_reporting_reasons` · `videos_nondestructive_trimming.clip_trim` · `videos_recommendations.get_related_videos` · `videos_showcases.add_or_remove_multiple_albums` · `videos_showcases.get_video_albums` · `videos_text_tracks.delete_text_track` · `videos_text_tracks.get_text_track` · `videos_transcripts.get_transcript` · `videos_transcripts.get_transcript_metadata` · `videos_uploads.complete_streaming_upload` · `videos_uploads.upload_video` · `videos_uploads.upload_video_alt1` · `videos_versions.create_video_version` · `videos_versions.edit_video_version` · `videos_versions.get_video_versions` · `videos_versions.get_video_versions_alt1` · `videos_video_comments.create_comment` · `videos_video_comments.create_comment_alt1` · `videos_video_comments.create_comment_reply` · `videos_video_comments.delete_comment` · `videos_video_comments.edit_comment`); distinguishing members `developer_message` · `error` · `error_code` · `link` - 3. `AuthError` — 2 operations (`authentication_extras_convert.convert_access_token` · `authentication_extras_exchange.exchange_auth_code`); distinguishing members `error` · `error_description` - 4. *(none)* — `api_apps_webhooks.get_webhooks` · `api_information_essentials.get_endpoints` · `categories_essentials.get_categories` · `categories_users.check_if_user_subscribed_to_category` · `categories_users.check_if_user_subscribed_to_category_alt1` · `categories_users.subscribe_to_category` · `categories_users.subscribe_to_category_alt1` · `categories_users.unsubscribe_from_category` · `categories_users.unsubscribe_from_category_alt1` · `channels_essentials.get_channel` · `channels_moderators.get_channel_moderator` · `channels_moderators.get_channel_moderators` · `embed_presets_essentials.create_embed_presets` · `embed_presets_essentials.create_embed_presets_alt1` · `embed_presets_essentials.get_embed_preset` · `embed_presets_essentials.get_embed_preset_alt1` · `embed_presets_essentials.get_embed_presets` · `embed_presets_essentials.get_embed_presets_alt1` · `embed_presets_videos.add_video_embed_preset` · `embed_presets_videos.get_embed_preset_videos` · `embed_presets_videos.get_embed_preset_videos_alt1` · `folders_items.get_folder_items` · `folders_items.get_folder_items2` · `likes_essentials.get_video_likes` · `likes_essentials.get_video_likes_alt1` · `live_essentials.get_live_event_occurrences` · `live_essentials.get_live_events` · `live_essentials.get_live_events_alt1` · `live_essentials.get_live_events_alt2` · `live_event_sessions.get_live_clip_ingest_status` · `live_event_sessions.get_live_ingest_status` · `on_demand_essentials.create_vod` · `on_demand_essentials.create_vod_alt1` · `on_demand_posters.edit_vod_poster` · `on_demand_posters.get_vod_poster` · `on_demand_regions.get_regions` · `on_demand_seasons.get_vod_season_videos` · `on_demand_videos.get_vod_video` · `on_demand_videos.get_vod_videos` · `payments_essentials.get_subscription_info` · `portfolios_essentials.get_portfolio` · `portfolios_essentials.get_portfolio_alt1` · `portfolios_essentials.get_portfolios` · `portfolios_essentials.get_portfolios_alt1` · `portfolios_videos.get_portfolio_video` · `portfolios_videos.get_portfolio_video_alt1` · `portfolios_videos.get_portfolio_videos` · `portfolios_videos.get_portfolio_videos_alt1` · `showcases_essentials.update_showcases` · `subscription_plans_essentials.get_plan` · `tags_essentials.get_tag` · `teams_members.get_team_information` · `teams_members.get_team_information2` · `teams_members.get_team_role_information` · `tutorial_essentials.developer_tutorial` · `users_analytics.get_user_analytics` · `users_analytics.get_user_analytics_alt1` · `users_essentials.get_user` · `users_essentials.get_user_alt1` · `users_followers.get_followers` · `users_followers.get_followers_alt1` · `users_followers.get_user_following` · `users_followers.get_user_following_alt1` · `users_followers.unfollow_user` · `users_followers.unfollow_user_alt1` · `users_lms.download_lms_package` · `users_pictures.create_picture` · `users_pictures.create_picture_alt1` · `users_pictures.delete_picture` · `users_pictures.delete_picture_alt1` · `users_pictures.edit_picture` · `users_pictures.edit_picture_alt1` · `users_pictures.get_picture` · `users_pictures.get_picture_alt1` · `users_pictures.get_pictures` · `users_pictures.get_pictures_alt1` · `users_watch_history.delete_from_watch_history` · `users_watch_history.delete_watch_history` · `videos_content_ratings.get_content_ratings` · `videos_creative_commons.get_cc_licenses` · `videos_credits.available_users` · `videos_credits.get_video_credits` · `videos_credits.get_video_credits_alt1` · `videos_essentials.get_videos` · `videos_essentials.get_videos_alt1` · `videos_fragments.delete_fragments` · `videos_fragments.get_fragments` · `videos_fragments.put_fragments` · `videos_languages.get_languages` · `videos_tags.get_video_tags` · `videos_thumbnails.create_video_thumbnail` · `videos_thumbnails.create_video_thumbnail_alt1` · `videos_thumbnails.delete_video_thumbnail` · `videos_thumbnails.edit_video_thumbnail` · `videos_thumbnails.get_video_thumbnail` · `videos_thumbnails.get_video_thumbnails` · `videos_thumbnails.get_video_thumbnails_alt1` · `videos_unlisted_videos.add_video_privacy_users` · `videos_unlisted_videos.add_video_privacy_users_alt1` · `videos_uploads.get_upload_attempt` · `videos_versions.get_audiotrack_downloads` · `videos_versions.get_version_thumbnail` · `videos_video_comments.get_comments` · `videos_video_comments.get_comments_alt1` · `watch_later_queue_essentials.add_video_to_watch_later` · `watch_later_queue_essentials.add_video_to_watch_later_alt1` · `watch_later_queue_essentials.delete_video_from_watch_later` · `watch_later_queue_essentials.delete_video_from_watch_later_alt1` · `watch_later_queue_essentials.get_watch_later_queue` · `watch_later_queue_essentials.get_watch_later_queue_alt1`: no typed arm, so `.error` is always `RawError` - 5. So `isinstance(e.error, LegacyError)` matches only 237 of 520 operations. -6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 212 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. -7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. -8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. -9. **Which host the `base_url` selects**, because omitting it is silently the default. -10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. - +--- +name: "python-getting-started" +description: "Vimeo API Python SDK identity and lookup layer (Python only) — install, import root, base URL/environments, the auth pattern, the SDK map that ships at the SDK root (`sdk-map.md` + `map/operations/`) and how to traverse it, and the module table naming the one file owning each fact the map leaves to the source. Load this before answering any Vimeo API Python SDK contract question or writing any SDK code." +--- + +# Getting started with the Vimeo API Python SDK + +> **Who this skill is for.** This is the **lookup layer** for anyone writing Vimeo API Python SDK code — it is yours to follow directly and fully. Ground every contract fact here (in the SDK map, and in the source modules it names) rather than in recall, and carry those facts onto a contract sheet before you implement. Load `python-integrate-vimeo-api` for the workflow that wraps this skill. + +This is the **SDK-specific** entry point. For general patterns that apply to any APIMatic-generated Python SDK (client construction, auth, calling endpoints, models, error handling, resilience, testing), see the companion API-agnostic skills: `python-client-initialization`, `python-authentication`, `python-calling-endpoints`, `python-models`, `python-error-handling`, `python-configuration-resilience` and `python-testing`. + +**This page and those companion skills are complementary — load both.** This page is authoritative for the SDK's *identity and surface* (what to install, what to import, the controllers, which module owns which fact); the companion skills are the *usage layer* on top — the best-practice way to call each piece and the gotchas a signature cannot show. Reading a module in the installed package does not remove the need to load the skill for that step, so at each step below, load the companion *and* confirm names against the installed package. + +## SDK identity + +Verified against `vimeo_api/` and `pyproject.toml` of the generated package at version `3.4.9`. **Re-verify after a version bump** — this page is a snapshot, not a live read. + +| Fact | Value | +| --- | --- | +| API | Vimeo API | +| Distribution name (what you install) | `vimeo-api` — **not on any package index**; installed from source (see *Install*) | +| Import root (what you import) | `vimeo_api` — not the string you install | +| Source repository | https://github.com/context-plugins/vimeo-python-sdk | +| Source branch | `main` | +| Version | `3.4.9` | +| Sync client class | `VimeoApiClient` (alias `Client`) | +| Async client class | `AsyncVimeoApiClient` (alias `AsyncClient`) | +| Client construction | **keyword-only**: `base_url` · `timeout` (default `30.0`) · `retry_options` · `bearer` · `oauth2_authorization_code` · `oauth2_authorization_code_token_source` · `oauth2_client_credentials` · `oauth2_client_credentials_token_source`, and the transport override — `custom_http_client` on the sync client, `custom_async_http_client` on the async one (the names differ; see step 1) | +| Auth | **Bearer** token — set `bearer` · **OAuth 2.0** authorization code — set `oauth2_authorization_code` · **OAuth 2.0** client credentials — set `oauth2_client_credentials` | +| Environments | **no environment enum** — one `base_url` string, defaulting to `https://api.vimeo.com` | +| Base-URL config | `ServerConfig` (`vimeo_api/server/server_config.py`), frozen, `extra="forbid"` | +| Python floor | **`>=3.10`** (classifiers list `3.10–3.14`) | +| Runtime dependencies | `httpx2 (>=2.13.1,<3.0.0)` · `certifi (>=2024.7.4)` · `pydantic[email] (>=2.11.0,<3.0.0)` · `typing-extensions (>=4.13.0,<5.0.0)` | +| Typing | ships `py.typed`; the package is checked under `mypy --strict` with `warn_unreachable`. Callers get full inference — **a type error against this SDK is a real contract violation, not noise** | +| Line length / lint | `ruff`, 120 cols (only relevant when editing the SDK itself) | +| Surface | 520 operations across 100 controllers · 784 models · 1 union · 281 enums · 411 per-operation error unions | + +The table above is **orientation, not a copy-paste recipe** — it gives you the names and facts (install, import roots, the auth *pattern*, the base-URL knob), while the actual integration code comes from the companion skills. Load each one as you reach its step (see **Integration workflow** below) and confirm its types against the installed package. + +## Install — from source + +Into the same environment your project runs in, install the distribution from its repository, — it is not published to a package index: + +```bash +pip install "vimeo-api @ git+https://github.com/context-plugins/vimeo-python-sdk.git@main" +python -c "import vimeo_api, pathlib; print(pathlib.Path(vimeo_api.__file__).parent)" +``` + +**Run the second line too.** A `pip install` that reports success is not proof the SDK is importable where your project runs. If the probe raises `ModuleNotFoundError`, the install went into a different interpreter: re-run it through the one your project uses (`python -m pip install …`) and probe again. If it prints a path whose contents are not this SDK, what you installed is not this distribution. + +The distribution carries its own `pyproject.toml`, so `pip` builds and installs it like any released package, copied into site-packages. Do not vendor its source into your project, add its directory to `sys.path`, or install it editable (`-e`) from a throwaway clone — an editable install points at that directory, so deleting the clone breaks every import. + +Once the second line prints this SDK's directory, write the imports from the table above: the distribution name you install and the package name you import are not the same string. Requires Python 3.10 or newer. + +## Imports — the package splits its surface across four modules + +Python does not re-export child modules transitively, so `from vimeo_api import models` alone does **not** make enums, error unions, or runtime types reachable. Import each kind of type from the module that owns it. + +`vimeo_api/__init__.py` exports exactly 5 names beside the `vimeo_api.models` subpackage it re-exports: + +```python +from vimeo_api import ( + AsyncClient, + AsyncVimeoApiClient, + Client, + ServerConfig, + VimeoApiClient, +) +``` + +Everything else comes from its own subpackage, and the split matters because each kind of type a caller reaches for lives in a different module: + +| What you need | Where it lives | +| --- | --- | +| Domain models, their `…Dict` companions | `vimeo_api.models` | +| Enums (and their open `…OrStr`/`…OrInt` aliases) | `vimeo_api.models.enums` | +| `ApiError` · `RawError` · `ApiResult` · `RequestOptions` · `AuthorizationCodeCredentials` · `ClientCredentials` · `HttpClient` · `SdkBaseModel` · `UNSET` · `Optional` | `vimeo_api.core` | +| Per-operation error *unions* | `vimeo_api.errors` (`ActivateLiveEventErrorBody`, …) | + +`vimeo_api.core` re-exports its whole public surface (a curated `__all__`), so import from `…core` rather than from the private modules beneath it (`…core.results`, `…core.exceptions`, `…core.auth.schemes`). + +## Environments — there is no environment enum + +This SDK has **no environment type and no environment constants**. There is one knob: `base_url`, on `ServerConfig` (`vimeo_api/server/server_config.py`), and its default is **`https://api.vimeo.com`**: + +```python +base_url: str = "https://api.vimeo.com" +``` + +Consequences to state on every contract sheet that touches configuration: + +- Omitting `base_url` gives you **`https://api.vimeo.com`**, silently. A caller who believes they configured a different host and did not gets that host's behaviour with live credentials. +- The token endpoint is derived from the same base URL (`/oauth/access_token`), so it moves only with `base_url` — you never configure it separately. +- The token endpoint is derived from the same base URL (`/oauth/authorize/client`), so it moves only with `base_url` — you never configure it separately. +- `ServerConfig` is a frozen pydantic model with `extra="forbid"`: a misspelled keyword raises `ValidationError` at construction rather than being ignored. +- `timeout` is validated too — the base client raises `ValueError` for any non-positive value. + +## Auth pattern (three schemes) + +A bearer token, exposed as the client's `bearer=` keyword taking a plain string. + +```python +from vimeo_api import Client + +client = Client(bearer="") +``` + +OAuth 2.0 authorization code, exposed as the client's `oauth2_authorization_code=` keyword taking `AuthorizationCodeCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/oauth/access_token`. + +```python +from vimeo_api import Client +from vimeo_api.core import AuthorizationCodeCredentials + +def prompt(url: str) -> str: + return input(f"Open {url}, then paste the code: ") + +client = Client(oauth2_authorization_code=AuthorizationCodeCredentials(client_id="…", client_secret="…", redirect_uri="…", prompt_for_authorization_code=prompt)) +client = Client(oauth2_authorization_code={"client_id": "…", "client_secret": "…", "redirect_uri": "…", "prompt_for_authorization_code": prompt}) # equivalent +``` + +OAuth 2.0 client credentials, exposed as the client's `oauth2_client_credentials=` keyword taking `ClientCredentials` **or a plain dict**. The client fetches and caches the bearer token itself, lazily, from `/oauth/authorize/client`. + +```python +from vimeo_api import Client +from vimeo_api.core import ClientCredentials + +client = Client(oauth2_client_credentials=ClientCredentials(client_id="…", client_secret="…")) +client = Client(oauth2_client_credentials={"client_id": "…", "client_secret": "…"}) # equivalent +``` + +**Every credentials keyword is optional at the type level and that is a trap worth flagging on every sheet.** Omit it and the client is built with `no_auth`: every request goes out unauthenticated. Nothing fails at construction. Most APIs then answer `401`; an API that serves anonymous traffic at all answers `200` and hides the omission entirely — verify the keyword is set rather than waiting for a `401` to tell you. + +See `python-authentication` for the full picture, including what a *failed token fetch* raises — it is not what a caller expects, and it is the single most common surprise in this SDK. + +## Controllers + +Controllers and their operation counts (`client.`): + +| Attribute | Class | Ops | Area | +| --- | --- | --- | --- | +| `client.api_apps_webhooks` | `ApiAppsWebhooks` / `AsyncApiAppsWebhooks` | 5 | `add_webhook` · `delete_webhook` · `get_webhook` · `get_webhooks` · `update_webhook` | +| `client.api_information_essentials` | `ApiInformationEssentials` / `AsyncApiInformationEssentials` | 1 | `get_endpoints` | +| `client.authentication_extras_authenticate` | `AuthenticationExtrasAuthenticate` / `AsyncAuthenticationExtrasAuthenticate` | 1 | `client_auth` | +| `client.authentication_extras_convert` | `AuthenticationExtrasConvert` / `AsyncAuthenticationExtrasConvert` | 1 | `convert_access_token` | +| `client.authentication_extras_essentials` | `AuthenticationExtrasEssentials` / `AsyncAuthenticationExtrasEssentials` | 2 | `delete_token` · `verify_token` | +| `client.authentication_extras_exchange` | `AuthenticationExtrasExchange` / `AsyncAuthenticationExtrasExchange` | 1 | `exchange_auth_code` | +| `client.categories_channels` | `CategoriesChannels` / `AsyncCategoriesChannels` | 1 | `get_category_channels` | +| `client.categories_essentials` | `CategoriesEssentials` / `AsyncCategoriesEssentials` | 2 | `get_categories` · `get_category` | +| `client.categories_groups` | `CategoriesGroups` / `AsyncCategoriesGroups` | 1 | `get_category_groups` | +| `client.categories_users` | `CategoriesUsers` / `AsyncCategoriesUsers` | 8 | `check_if_user_subscribed_to_category` · `check_if_user_subscribed_to_category_alt1` · `get_category_subscriptions` · `get_category_subscriptions_alt1` · `subscribe_to_category` · `subscribe_to_category_alt1` · … | +| `client.categories_videos` | `CategoriesVideos` / `AsyncCategoriesVideos` | 4 | `check_category_for_video` · `get_category_videos` · `get_video_categories` · `suggest_video_category` | +| `client.channels_categories` | `ChannelsCategories` / `AsyncChannelsCategories` | 4 | `add_channel_categories` · `categorize_channel` · `delete_channel_category` · `get_channel_categories` | +| `client.channels_essentials` | `ChannelsEssentials` / `AsyncChannelsEssentials` | 7 | `create_channel` · `delete_channel` · `edit_channel` · `get_channel` · `get_channel_subscriptions` · `get_channel_subscriptions_alt1` · … | +| `client.channels_moderators` | `ChannelsModerators` / `AsyncChannelsModerators` | 7 | `add_channel_moderator` · `add_channel_moderators` · `get_channel_moderator` · `get_channel_moderators` · `remove_channel_moderator` · `remove_channel_moderators` · … | +| `client.channels_private_channel_members` | `ChannelsPrivateChannelMembers` / `AsyncChannelsPrivateChannelMembers` | 4 | `delete_channel_privacy_user` · `get_channel_privacy_users` · `set_channel_privacy_user` · `set_channel_privacy_users` | +| `client.channels_subscriptions_and_subscribers` | `ChannelsSubscriptionsAndSubscribers` / `AsyncChannelsSubscriptionsAndSubscribers` | 7 | `check_if_user_subscribed_to_channel` · `check_if_user_subscribed_to_channel_alt1` · `get_channel_subscribers` · `subscribe_to_channel` · `subscribe_to_channel_alt1` · `unsubscribe_from_channel` · … | +| `client.channels_tags` | `ChannelsTags` / `AsyncChannelsTags` | 5 | `add_channel_tag` · `add_tags_to_channel` · `check_if_channel_has_tag` · `delete_tag_from_channel` · `get_channel_tags` | +| `client.channels_videos` | `ChannelsVideos` / `AsyncChannelsVideos` | 7 | `add_video_to_channel` · `add_videos_to_channel` · `delete_video_from_channel` · `get_available_video_channels` · `get_channel_video` · `get_channel_videos` · … | +| `client.embed_presets_custom_logos` | `EmbedPresetsCustomLogos` / `AsyncEmbedPresetsCustomLogos` | 8 | `create_custom_logo` · `create_custom_logo_alt1` · `delete_custom_logo` · `delete_custom_logo_alt1` · `get_custom_logo` · `get_custom_logo_alt1` · … | +| `client.embed_presets_essentials` | `EmbedPresetsEssentials` / `AsyncEmbedPresetsEssentials` | 10 | `create_embed_presets` · `create_embed_presets_alt1` · `delete_embed_preset` · `delete_embed_preset_alt1` · `edit_embed_preset` · `edit_embed_preset_alt1` · … | +| `client.embed_presets_folders` | `EmbedPresetsFolders` / `AsyncEmbedPresetsFolders` | 1 | `delete_folder_embed_preset` | +| `client.embed_presets_timeline_events` | `EmbedPresetsTimelineEvents` / `AsyncEmbedPresetsTimelineEvents` | 2 | `create_video_custom_logo` · `get_video_custom_logo` | +| `client.embed_presets_videos` | `EmbedPresetsVideos` / `AsyncEmbedPresetsVideos` | 5 | `add_video_embed_preset` · `delete_video_embed_preset` · `get_embed_preset_videos` · `get_embed_preset_videos_alt1` · `get_video_embed_preset` | +| `client.folders_essentials` | `FoldersEssentials` / `AsyncFoldersEssentials` | 11 | `create_project` · `create_project_alt1` · `delete_project` · `delete_project_alt1` · `edit_project` · `edit_project_alt1` · … | +| `client.folders_items` | `FoldersItems` / `AsyncFoldersItems` | 3 | `delete_folder_items_alt1` · `get_folder_items` · `get_folder_items2` | +| `client.folders_videos` | `FoldersVideos` / `AsyncFoldersVideos` | 10 | `add_video_to_project` · `add_video_to_project_alt1` · `add_videos_to_project` · `add_videos_to_project_alt1` · `get_project_videos` · `get_project_videos_alt1` · … | +| `client.groups_essentials` | `GroupsEssentials` / `AsyncGroupsEssentials` | 4 | `create_group` · `delete_group` · `get_group` · `get_groups` | +| `client.groups_subscriptions` | `GroupsSubscriptions` / `AsyncGroupsSubscriptions` | 4 | `join_group` · `join_group_alt1` · `leave_group` · `leave_group_alt1` | +| `client.groups_users` | `GroupsUsers` / `AsyncGroupsUsers` | 5 | `check_if_user_joined_group` · `check_if_user_joined_group_alt1` · `get_group_members` · `get_user_groups` · `get_user_groups_alt1` | +| `client.groups_videos` | `GroupsVideos` / `AsyncGroupsVideos` | 5 | `add_video_to_group` · `delete_video_from_group` · `get_available_video_groups` · `get_group_video` · `get_group_videos` | +| `client.likes_essentials` | `LikesEssentials` / `AsyncLikesEssentials` | 11 | `check_if_user_liked_video` · `check_if_user_liked_video_alt1` · `get_likes` · `get_likes_alt1` · `get_video_likes` · `get_video_likes_alt1` · … | +| `client.live_analytics` | `LiveAnalytics` / `AsyncLiveAnalytics` | 1 | `export_vpaas_viewer_analytics` | +| `client.live_audio_tracks` | `LiveAudioTracks` / `AsyncLiveAudioTracks` | 2 | `get_event_audio_tracks` · `update_event_audio_tracks` | +| `client.live_embed_privacy` | `LiveEmbedPrivacy` / `AsyncLiveEmbedPrivacy` | 6 | `get_live_event_whitelist` · `get_live_event_whitelist_alt1` · `get_live_event_whitelist_alt2` · `set_live_event_whitelist` · `set_live_event_whitelist_alt1` · `set_live_event_whitelist_alt2` | +| `client.live_essentials` | `LiveEssentials` / `AsyncLiveEssentials` | 19 | `create_live_event` · `create_live_event_alt1` · `create_live_event_alt2` · `delete_live_event` · `delete_live_event_alt1` · `delete_live_event_alt2` · … | +| `client.live_event_m3_u8_playback` | `LiveEventM3U8Playback` / `AsyncLiveEventM3U8Playback` | 2 | `get_live_event_m3u8_playback` · `get_live_event_m3u8_playback_alt1` | +| `client.live_event_activation` | `LiveEventActivation` / `AsyncLiveEventActivation` | 3 | `activate_live_event` · `activate_live_event_alt1` · `activate_live_event_alt2` | +| `client.live_event_automated_closed_captions` | `LiveEventAutomatedClosedCaptions` / `AsyncLiveEventAutomatedClosedCaptions` | 3 | `edit_live_event_auto_cc` · `edit_live_event_auto_cc_alt1` · `edit_live_event_auto_cc_alt2` | +| `client.live_event_destinations` | `LiveEventDestinations` / `AsyncLiveEventDestinations` | 12 | `create_live_event_destination` · `create_live_event_destination_alt1` · `create_ott_destination` · `delete_live_event_destination` · `delete_ott_destination` · `get_available_destinations` · … | +| `client.live_event_end` | `LiveEventEnd` / `AsyncLiveEventEnd` | 3 | `end_live_event` · `end_live_event_alt1` · `end_live_event_alt2` | +| `client.live_event_low_latency` | `LiveEventLowLatency` / `AsyncLiveEventLowLatency` | 3 | `toggle_rle_low_latency` · `toggle_rle_low_latency_alt1` · `toggle_rle_low_latency_alt2` | +| `client.live_event_sessions` | `LiveEventSessions` / `AsyncLiveEventSessions` | 2 | `get_live_clip_ingest_status` · `get_live_ingest_status` | +| `client.live_event_thumbnails` | `LiveEventThumbnails` / `AsyncLiveEventThumbnails` | 15 | `create_live_event_thumbnail` · `create_live_event_thumbnail_alt1` · `create_live_event_thumbnail_alt2` · `delete_live_event_thumbnail` · `delete_live_event_thumbnail_alt1` · `delete_live_event_thumbnail_alt2` · … | +| `client.live_event_videos` | `LiveEventVideos` / `AsyncLiveEventVideos` | 12 | `add_videos_to_live_event` · `add_videos_to_live_event_alt1` · `add_videos_to_live_event_alt2` · `get_live_event_video` · `get_live_event_video_alt1` · `get_live_event_video_alt2` · … | +| `client.live_graphics` | `LiveGraphics` / `AsyncLiveGraphics` | 3 | `add_composer_video_graphic_item` · `delete_composer_video_graphic_item` · `get_composer_video_graphic_items` | +| `client.live_scenes` | `LiveScenes` / `AsyncLiveScenes` | 5 | `attach_graphic_item_to_scene` · `create_composer_scene` · `get_composer_scenes` · `reset_composer_scenes` · `update_composer_scene` | +| `client.on_demand_backgrounds` | `OnDemandBackgrounds` / `AsyncOnDemandBackgrounds` | 5 | `create_vod_background` · `delete_vod_background` · `edit_vod_background` · `get_vod_background` · `get_vod_backgrounds` | +| `client.on_demand_essentials` | `OnDemandEssentials` / `AsyncOnDemandEssentials` | 7 | `create_vod` · `create_vod_alt1` · `delete_vod_draft` · `edit_vod` · `get_user_vods` · `get_user_vods_alt1` · … | +| `client.on_demand_genres` | `OnDemandGenres` / `AsyncOnDemandGenres` | 8 | `add_vod_genre` · `delete_vod_genre` · `get_genre_vod` · `get_genre_vods` · `get_vod_genre` · `get_vod_genre_by_ondemand_id` · … | +| `client.on_demand_posters` | `OnDemandPosters` / `AsyncOnDemandPosters` | 4 | `add_vod_poster` · `edit_vod_poster` · `get_vod_poster` · `get_vod_posters` | +| `client.on_demand_promotions` | `OnDemandPromotions` / `AsyncOnDemandPromotions` | 5 | `create_vod_promotion` · `delete_vod_promotion` · `get_vod_promotion` · `get_vod_promotion_codes` · `get_vod_promotions` | +| `client.on_demand_purchases_and_rentals` | `OnDemandPurchasesAndRentals` / `AsyncOnDemandPurchasesAndRentals` | 3 | `check_if_vod_was_purchased_alt1` · `get_vod_purchases` · `get_vod_purchases_alt1` | +| `client.on_demand_regions` | `OnDemandRegions` / `AsyncOnDemandRegions` | 8 | `add_vod_region` · `delete_vod_region` · `delete_vod_regions` · `get_region` · `get_regions` · `get_vod_region` · … | +| `client.on_demand_seasons` | `OnDemandSeasons` / `AsyncOnDemandSeasons` | 3 | `get_vod_season` · `get_vod_season_videos` · `get_vod_seasons` | +| `client.on_demand_videos` | `OnDemandVideos` / `AsyncOnDemandVideos` | 4 | `add_video_to_vod` · `delete_video_from_vod` · `get_vod_video` · `get_vod_videos` | +| `client.payments_essentials` | `PaymentsEssentials` / `AsyncPaymentsEssentials` | 4 | `delete_payment_method` · `get_payment_method_info` · `get_subscription_info` · `list_payment_methods` | +| `client.portfolios_essentials` | `PortfoliosEssentials` / `AsyncPortfoliosEssentials` | 4 | `get_portfolio` · `get_portfolio_alt1` · `get_portfolios` · `get_portfolios_alt1` | +| `client.portfolios_videos` | `PortfoliosVideos` / `AsyncPortfoliosVideos` | 8 | `add_video_to_portfolio` · `add_video_to_portfolio_alt1` · `delete_video_from_portfolio` · `delete_video_from_portfolio_alt1` · `get_portfolio_video` · `get_portfolio_video_alt1` · … | +| `client.search_federated` | `SearchFederated` / `AsyncSearchFederated` | 2 | `federated_search_user_items` · `federated_search_user_items_alt1` | +| `client.showcases_custom_showcase_logos` | `ShowcasesCustomShowcaseLogos` / `AsyncShowcasesCustomShowcaseLogos` | 5 | `create_showcase_logo` · `delete_showcase_logo` · `get_showcase_logo` · `get_showcase_logos` · `replace_showcase_logo` | +| `client.showcases_custom_showcase_thumbnails` | `ShowcasesCustomShowcaseThumbnails` / `AsyncShowcasesCustomShowcaseThumbnails` | 5 | `create_showcase_custom_thumb` · `delete_showcase_custom_thumbnail` · `get_showcase_custom_thumbnail` · `get_showcase_custom_thumbs` · `replace_showcase_custom_thumb` | +| `client.showcases_essentials` | `ShowcasesEssentials` / `AsyncShowcasesEssentials` | 14 | `add_folder_to_showcases` · `copy_showcase` · `copy_showcase_alt2` · `create_showcase` · `create_showcase_alt1` · `delete_showcase` · … | +| `client.showcases_showcase_videos` | `ShowcasesShowcaseVideos` / `AsyncShowcasesShowcaseVideos` | 16 | `add_video_to_showcase` · `add_video_to_showcase_alt2` · `get_available_showcase_videos` · `get_available_video_showcases` · `get_showcase_video` · `get_showcase_video_alt2` · … | +| `client.subscription_plans_essentials` | `SubscriptionPlansEssentials` / `AsyncSubscriptionPlansEssentials` | 1 | `get_plan` | +| `client.tags_essentials` | `TagsEssentials` / `AsyncTagsEssentials` | 1 | `get_tag` | +| `client.teams_essentials` | `TeamsEssentials` / `AsyncTeamsEssentials` | 5 | `backfill_team_custom_metadata` · `get_team_custom_metadata_incomplete_videos` · `get_team_custom_metadata_settings` · `update_custom_metadata_field_options` · `upsert_custom_metadata_settings` | +| `client.teams_members` | `TeamsMembers` / `AsyncTeamsMembers` | 3 | `get_team_information` · `get_team_information2` · `get_team_role_information` | +| `client.tutorial_essentials` | `TutorialEssentials` / `AsyncTutorialEssentials` | 1 | `developer_tutorial` | +| `client.users_analytics` | `UsersAnalytics` / `AsyncUsersAnalytics` | 2 | `get_user_analytics` · `get_user_analytics_alt1` | +| `client.users_essentials` | `UsersEssentials` / `AsyncUsersEssentials` | 4 | `edit_user` · `edit_user_alt1` · `get_user` · `get_user_alt1` | +| `client.users_feeds` | `UsersFeeds` / `AsyncUsersFeeds` | 2 | `get_feed` · `get_feed_alt1` | +| `client.users_followers` | `UsersFollowers` / `AsyncUsersFollowers` | 12 | `check_if_user_is_following` · `check_if_user_is_following_alt1` · `follow_user` · `follow_user_alt1` · `follow_users` · `follow_users_alt1` · … | +| `client.users_lms` | `UsersLms` / `AsyncUsersLms` | 1 | `download_lms_package` | +| `client.users_pictures` | `UsersPictures` / `AsyncUsersPictures` | 10 | `create_picture` · `create_picture_alt1` · `delete_picture` · `delete_picture_alt1` · `edit_picture` · `edit_picture_alt1` · … | +| `client.users_search` | `UsersSearch` / `AsyncUsersSearch` | 1 | `search_users` | +| `client.users_watch_history` | `UsersWatchHistory` / `AsyncUsersWatchHistory` | 3 | `delete_from_watch_history` · `delete_watch_history` · `get_watch_history` | +| `client.videos_ai` | `VideosAi` / `AsyncVideosAi` | 11 | `ask_ai_question` · `edit_ai_texttrack` · `get_ai_languages` · `get_ai_metadata` · `get_ai_questions` · `get_ai_transcribe_status` · … | +| `client.videos_animated_thumbnails` | `VideosAnimatedThumbnails` / `AsyncVideosAnimatedThumbnails` | 5 | `create_animated_thumbset` · `delete_animated_thumbset` · `get_all_animated_thumbset` · `get_animated_thumbset` · `get_animated_thumbset_status` | +| `client.videos_chapters` | `VideosChapters` / `AsyncVideosChapters` | 12 | `create_chapter` · `create_chapter_thumbnail_or_upload_link` · `create_unsaved_chapter_thumbnail_or_upload_link` · `delete_chapter` · `delete_chapter_thumbnail` · `edit_chapter` · … | +| `client.videos_content_ratings` | `VideosContentRatings` / `AsyncVideosContentRatings` | 1 | `get_content_ratings` | +| `client.videos_creative_commons` | `VideosCreativeCommons` / `AsyncVideosCreativeCommons` | 1 | `get_cc_licenses` | +| `client.videos_credits` | `VideosCredits` / `AsyncVideosCredits` | 8 | `add_video_credit` · `add_video_credit_alt1` · `available_users` · `delete_video_credit` · `edit_video_credit` · `get_video_credit` · … | +| `client.videos_embed_privacy` | `VideosEmbedPrivacy` / `AsyncVideosEmbedPrivacy` | 3 | `add_video_privacy_domain` · `delete_video_privacy_domain` · `get_video_privacy_domains` | +| `client.videos_essentials` | `VideosEssentials` / `AsyncVideosEssentials` | 15 | `check_if_user_owns_video` · `check_if_user_owns_video_alt1` · `copy_video` · `copy_video_alt2` · `delete_video` · `delete_videos` · … | +| `client.videos_fragments` | `VideosFragments` / `AsyncVideosFragments` | 3 | `delete_fragments` · `get_fragments` · `put_fragments` | +| `client.videos_languages` | `VideosLanguages` / `AsyncVideosLanguages` | 1 | `get_languages` | +| `client.videos_live_m3_u8_playback` | `VideosLiveM3U8Playback` / `AsyncVideosLiveM3U8Playback` | 2 | `get_one_time_event_m3u8_playback` · `get_one_time_event_m3u8_playback_alt1` | +| `client.videos_moderation` | `VideosModeration` / `AsyncVideosModeration` | 1 | `get_video_reporting_reasons` | +| `client.videos_nondestructive_trimming` | `VideosNondestructiveTrimming` / `AsyncVideosNondestructiveTrimming` | 1 | `clip_trim` | +| `client.videos_recommendations` | `VideosRecommendations` / `AsyncVideosRecommendations` | 1 | `get_related_videos` | +| `client.videos_showcases` | `VideosShowcases` / `AsyncVideosShowcases` | 2 | `add_or_remove_multiple_albums` · `get_video_albums` | +| `client.videos_tags` | `VideosTags` / `AsyncVideosTags` | 6 | `add_video_tag` · `add_video_tags` · `check_video_for_tag` · `delete_video_tag` · `get_video_tags` · `get_videos_with_tag` | +| `client.videos_text_tracks` | `VideosTextTracks` / `AsyncVideosTextTracks` | 9 | `create_text_track` · `create_text_track_alt1` · `delete_text_track` · `edit_text_track` · `get_text_track` · `get_text_tracks` · … | +| `client.videos_thumbnails` | `VideosThumbnails` / `AsyncVideosThumbnails` | 7 | `create_video_thumbnail` · `create_video_thumbnail_alt1` · `delete_video_thumbnail` · `edit_video_thumbnail` · `get_video_thumbnail` · `get_video_thumbnails` · … | +| `client.videos_transcripts` | `VideosTranscripts` / `AsyncVideosTranscripts` | 2 | `get_transcript` · `get_transcript_metadata` | +| `client.videos_unlisted_videos` | `VideosUnlistedVideos` / `AsyncVideosUnlistedVideos` | 6 | `add_video_privacy_user` · `add_video_privacy_users` · `add_video_privacy_users_alt1` · `delete_video_privacy_user` · `get_video_privacy_users` · `get_video_privacy_users_alt1` | +| `client.videos_uploads` | `VideosUploads` / `AsyncVideosUploads` | 4 | `complete_streaming_upload` · `get_upload_attempt` · `upload_video` · `upload_video_alt1` | +| `client.videos_versions` | `VideosVersions` / `AsyncVideosVersions` | 13 | `create_audio_track` · `create_video_version` · `delete_audio_track` · `delete_video_version` · `edit_audio_track` · `edit_video_version` · … | +| `client.videos_video_comments` | `VideosVideoComments` / `AsyncVideosVideoComments` | 9 | `create_comment` · `create_comment_alt1` · `create_comment_reply` · `delete_comment` · `edit_comment` · `get_comment` · … | +| `client.watch_later_queue_essentials` | `WatchLaterQueueEssentials` / `AsyncWatchLaterQueueEssentials` | 8 | `add_video_to_watch_later` · `add_video_to_watch_later_alt1` · `check_watch_later_queue` · `check_watch_later_queue_alt1` · `delete_video_from_watch_later` · `delete_video_from_watch_later_alt1` · … | + +Every controller has an `Async…` peer whose operations are identical in name and parameters and differ solely by being awaited. Do not emit a separate row for an async operation; state the rule once on the sheet. + +The SDK map's controller table carries the same counts and links to a page per controller (`map/operations/.md`) — go there for the operations themselves. + +## SDK map — look up first, open the module second + +The SDK ships a generated map at its **root** — the directory holding `pyproject.toml`, the `vimeo_api/` source directory, and these two entries: + +- **`sdk-map.md`** — the index: client construction with the full constructor-keyword table, the error-handling model (`ApiError` / `ApiResult` / `RawError`, Case A vs Case B), where models, enums and error aliases live, servers and auth, and the link table into the operations pages. +- **`map/operations/.md`** — one page per controller, one `###` block per operation: the HTTP verb and route, the sync parsed signature, each parameter's role and wire name, both return types, the error alias with the status each arm maps from, and a **Type sources** table naming the module that declares every *generated* type the operation mentions. + +**Installing the distribution does not give you the map.** `pyproject.toml` ships the `vimeo_api/` package only, so `sdk-map.md` and `map/operations/` are absent from the installed package — they live in the SDK's own source tree, at the root of its repository, , in lockstep with the code the map describes — clone it to a temporary directory, outside the project repo: + +```bash +git clone --depth 1 --branch main https://github.com/context-plugins/vimeo-python-sdk +``` + +Confirm the map comes from the release you installed before you read anything out of it: the clone above is pinned to `main`, the branch this SDK is released from — the repository's default branch may carry a different version. A map read from any other release describes code you do not have. + +Every `Source` path on the map is relative to that SDK root, so `vimeo_api/models/api_app.py` opens as written from there — and the same path resolves inside the installed package, which is where you read a module's body once the map has named it. + +**The map is the locator; the source modules are the shapes.** Read the map first — signatures, routes, parameter roles, return types, error unions, and which module declares a type are all answered there without opening a single `.py` file. Then open the one module the map names for what it deliberately does not carry: a model's members, an enum's values, a field's wire alias. The map says so itself — *"Shapes live only in the source … Never grep for a type."* + +**The map carries shapes; what an operation *means* lives elsewhere.** When *what* to pass depends on meaning — which values a field accepts beyond its type, a rule that couples two fields, what a defaulted parameter actually selects — the map will not settle it. Open the operation's docstring in `vimeo_api/apis/.py` (or `client.py` where operations sit on the client) and `api-reference.md` at the SDK root for that operation *before* writing the sheet row, and record what you found there. A value you already "know" for a field the map types as a plain `str` is a lookup, not a recall — the memory ban applies to it. + +`sdk-map.md` carries the invariants every operation block assumes, so load it before any `map/operations/` page; the pages are written to be read beside it. + +## Contract facts — the map first, then the module + +**Six of these are map lookups — don't open the module for them:** an operation's signature, parameters and return type; the client's constructor keywords; the `timeout` default; the members of `ApiError` / `Success` / `Failure` / `RawError`; an operation's error union and the status each arm maps from; base-URL and auth wiring. The table below covers everything else, and the full body behind a map row. + +Read the one module that owns the fact **inside the installed package**. Locate it first: + +```bash +python -c "import vimeo_api, pathlib; print(pathlib.Path(vimeo_api.__file__).parent)" +``` + +Failing that, it is under the project's environment (`.venv/Lib/site-packages/vimeo_api` on Windows, `.venv/lib/python3.*/site-packages/vimeo_api` elsewhere). **If the package is not installed, install it first** — see *Install* above; until then mark the fact `UNVERIFIED` and say what would settle it rather than answering from memory. Paths below are relative to that package root: + +| Question | Module | +| --- | --- | +| An operation's real signature, parameters and return type | `apis/.py` | +| Client construction, keywords, controller wiring | `client.py`, `async_client.py`, `base_client.py` | +| Timeout default and validation | `base_client.py` (`DEFAULT_TIMEOUT = 30.0`) | +| The request/response pipeline, 401 handling, 2xx-vs-error split | `core/raw_client.py` | +| Exception shape (`ApiError.error`, `.status_code`, `.headers`) | `core/exceptions.py` | +| `Success`/`Failure`/`RawError` | `core/results.py` | +| Per-call overrides | `core/request_options.py` | +| `UNSET`, `Optional`, `OptionalNullable` | `core/optionality.py` | +| Model base config, `to_dict`/`to_json` | `core/models.py` | +| A model's members, required vs `UNSET`, wire aliases | `models/.py` | +| An enum's members and wire values | `models/enums/` | +| Open-enum coercion | `core/converters/open_enum.py` | +| Date/time wire formats — `Date`, `RFC3339DateTime`, `RFC1123DateTime`, `UnixSecondsDateTime` (`Annotated` aliases over `datetime.date` / `datetime.datetime`; not in the map's Type sources) | `core/converters/date_time.py` | +| Transport protocols (the test seam) | `core/transport.py` | +| httpx2 adapter, proxy/TLS knobs | `core/httpx2_transport.py` | +| Token fetch, credential placement | `core/auth/`, `core/auth/models.py` | +| Base-URL resolution | `server/server_config.py`, `server/server.py` | +| An operation's error mapper (status → schema) | `errors/_error.py` | + +**Read scoped.** These modules carry long design docstrings; `grep -n` for the symbol and read the surrounding lines rather than whole files. Never quote a docstring's design rationale onto a contract sheet — the sheet carries facts an implementer must obey, not the reasoning behind them. + +Keep lookups cheap — the rules that keep a session's context small: + +- Collect the contracts for **every** in-scope operation in **one** pass — signature, required members with wire aliases, the error union, enum values — into a short **contract sheet** in your plan or working notes, then implement from the sheet. Don't re-open a module per member, and never re-look-up a fact the sheet already carries. +- Recurse into a model's members only where the task actually sets them — a full transitive expansion is hundreds of rows and nobody needs it. +- Trust the interpreter over this page: if a name here ever fails to type-check or import, re-read the module the table above names and report the drift; never patch around it from memory. + +## Integration workflow — load the companion skill at each step + +Before you write the code for each step, load the named companion skill — even if you have already read the relevant module. Each step calls out the trap the signature hides (in *parens*). A typical integration reaches them in this order: + +1. **Client construction & lifetime** — load **python-client-initialization** before you write `Client(...)` or `AsyncClient(...)`. (*The signature won't tell you:* the constructor is keyword-only, so nothing can be passed positionally; the client owns an `httpx2` connection pool and you **must** `close()` (sync) or `await aclose()` (async) or use it as a context manager; it must be long-lived and module- or app-scoped, never rebuilt per request; the sync and async clients do not mix; and the transport-override keyword differs by client — `custom_http_client` vs `custom_async_http_client`.) +2. **Authentication** — load **python-authentication** before you set credentials. The three schemes are `bearer=`, `oauth2_authorization_code=` and `oauth2_client_credentials=`. (*The signature won't tell you:* every credentials keyword is *optional* — omit it and every request goes out unauthenticated, with no failure at construction and not necessarily a `401` to tell you; the token is fetched lazily and cached by the client; and a **failed token fetch** raises something other than what a caller expects and **bypasses the non-raising response mode** entirely. Load secrets from the environment or a secret store, never hardcode.) +3. **Calling an endpoint** — load **python-calling-endpoints** before the first `client..(...)` call. (*The signature won't tell you:* every operation splits positional path params (and sometimes the body) from a keyword-only tail after `*`; every keyword-only parameter has a **real** default, so there is no "must pass `None` explicitly" hazard; **174 operations return `None`**, so `with_raw_response` is the only way to observe their status code; and the two response modes — raising vs `ApiResult` — behave differently on failure.) +4. **Models** — load **python-models** the moment a request/response member is not a plain string or number. (*The signature won't tell you:* `Optional[T]` here is `T | UnsetType`, **not** `typing.Optional` — `None` is not a legal value for it; models are frozen pydantic instances with `…Dict` TypedDict companions; enums are **open** (`…OrStr`/`…OrInt`), so an unknown wire value passes through as a plain `str`/`int` rather than raising; wire aliases differ from Python member names; unknown response fields are **preserved**, not dropped; and serialize via `to_dict`/`to_json`.) +5. **Error handling** — load **python-error-handling** before you write any `try/except`. (*The signature won't tell you:* there is a single `ApiError` type whose `.error` is a **per-operation union**, and a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, and bypasses both response modes; `httpx2` transport exceptions reach your boundary unwrapped.) +6. **Configuration & resilience** — load **python-configuration-resilience** when you set the base URL, timeouts, retries, proxies, TLS, or logging. (*The signature won't tell you:* **retrying is on by default** — `retry_options` takes a count, a `RetryOptions` or its dict, `max_retries` set to `0` turns it off, and one call overrides it through `request_options`; a `401` is never retried; `timeout` defaults to `30.0` and is a single float that maps onto `httpx2`'s timeout semantics, limiting each wait rather than the whole call; and there is no logging hook — you wrap the transport seam.) +7. **Testing** — load **python-testing** before you stub the SDK. (*The signature won't tell you:* the seam is the **transport protocol** (`HttpClient`/`AsyncHttpClient` in `core/transport.py`) passed as `custom_http_client` — not the client class; a fake transport implements `send` and `close`, the two the protocol declares, and `send` answers with the head alone; assert on the request the SDK actually built, and cover all four failure kinds, decode failures included.) + +## What a contract sheet must carry for this SDK + +Beyond the usual signatures and model members, a Python sheet is incomplete without these, because each one is a decision the implementer cannot make correctly from the signature alone: + +1. **Sync or async** — which client class, and the reminder that the two do not mix. Plus the `close()`/`aclose()` obligation and where the client is held. +2. **The keyword-only boundary** for each operation: what is positional (path params, sometimes the body) and what sits after `*`. Every keyword-only parameter has a real default, so there is no "must pass `None` explicitly" hazard — say so, so nobody writes defensive `None`s. +3. **The 174 operations that return `None`** — `api_apps_webhooks.delete_webhook` · `api_information_essentials.get_endpoints` · `authentication_extras_essentials.delete_token` · `categories_users.check_if_user_subscribed_to_category` · `categories_users.check_if_user_subscribed_to_category_alt1` · `categories_users.subscribe_to_category` · `categories_users.subscribe_to_category_alt1` · `categories_users.unsubscribe_from_category` · `categories_users.unsubscribe_from_category_alt1` · `channels_categories.add_channel_categories` · `channels_categories.categorize_channel` · `channels_categories.delete_channel_category` · `channels_essentials.delete_channel` · `channels_moderators.add_channel_moderator` · `channels_moderators.add_channel_moderators` · `channels_moderators.remove_channel_moderator` · `channels_moderators.remove_channel_moderators` · `channels_private_channel_members.delete_channel_privacy_user` · `channels_private_channel_members.set_channel_privacy_user` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel_alt1` · `channels_subscriptions_and_subscribers.subscribe_to_channel` · `channels_subscriptions_and_subscribers.subscribe_to_channel_alt1` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel_alt1` · `channels_tags.add_channel_tag` · `channels_tags.check_if_channel_has_tag` · `channels_tags.delete_tag_from_channel` · `channels_videos.add_video_to_channel` · `channels_videos.add_videos_to_channel` · `channels_videos.delete_video_from_channel` · `channels_videos.remove_videos_from_channel` · `embed_presets_custom_logos.delete_custom_logo` · `embed_presets_custom_logos.delete_custom_logo_alt1` · `embed_presets_essentials.delete_embed_preset` · `embed_presets_essentials.delete_embed_preset_alt1` · `embed_presets_folders.delete_folder_embed_preset` · `embed_presets_videos.add_video_embed_preset` · `embed_presets_videos.delete_video_embed_preset` · `embed_presets_videos.get_video_embed_preset` · `folders_essentials.delete_project` · `folders_essentials.delete_project_alt1` · `folders_items.delete_folder_items_alt1` · `folders_items.get_folder_items` · `folders_items.get_folder_items2` · `folders_videos.add_video_to_project` · `folders_videos.add_video_to_project_alt1` · `folders_videos.add_videos_to_project` · `folders_videos.add_videos_to_project_alt1` · `folders_videos.remove_video_from_project` · `folders_videos.remove_video_from_project_alt1` · `folders_videos.remove_videos_from_project` · `folders_videos.remove_videos_from_project_alt1` · `groups_essentials.delete_group` · `groups_subscriptions.join_group` · `groups_subscriptions.join_group_alt1` · `groups_subscriptions.leave_group` · `groups_subscriptions.leave_group_alt1` · `groups_users.check_if_user_joined_group` · `groups_users.check_if_user_joined_group_alt1` · `groups_videos.delete_video_from_group` · `likes_essentials.check_if_user_liked_video` · `likes_essentials.check_if_user_liked_video_alt1` · `likes_essentials.like_video` · `likes_essentials.like_video_alt1` · `likes_essentials.unlike_video` · `likes_essentials.unlike_video_alt1` · `live_analytics.export_vpaas_viewer_analytics` · `live_embed_privacy.set_live_event_whitelist` · `live_embed_privacy.set_live_event_whitelist_alt1` · `live_embed_privacy.set_live_event_whitelist_alt2` · `live_essentials.delete_live_event` · `live_essentials.delete_live_event_alt1` · `live_essentials.delete_live_event_alt2` · `live_essentials.delete_live_events` · `live_essentials.delete_live_events_alt1` · `live_essentials.delete_live_events_alt2` · `live_event_m3_u8_playback.get_live_event_m3u8_playback` · `live_event_m3_u8_playback.get_live_event_m3u8_playback_alt1` · `live_event_destinations.delete_live_event_destination` · `live_event_destinations.delete_ott_destination` · `live_event_destinations.get_available_destinations` · `live_event_destinations.get_available_destinations_alt1` · `live_event_thumbnails.delete_live_event_thumbnail` · `live_event_thumbnails.delete_live_event_thumbnail_alt1` · `live_event_thumbnails.delete_live_event_thumbnail_alt2` · `live_event_videos.add_videos_to_live_event` · `live_event_videos.add_videos_to_live_event_alt1` · `live_event_videos.add_videos_to_live_event_alt2` · `live_event_videos.remove_videos_from_live_event` · `live_event_videos.remove_videos_from_live_event_alt1` · `live_event_videos.remove_videos_from_live_event_alt2` · `live_graphics.delete_composer_video_graphic_item` · `on_demand_backgrounds.delete_vod_background` · `on_demand_essentials.delete_vod_draft` · `on_demand_genres.delete_vod_genre` · `on_demand_promotions.delete_vod_promotion` · `on_demand_regions.delete_vod_region` · `on_demand_videos.delete_video_from_vod` · `payments_essentials.delete_payment_method` · `payments_essentials.get_subscription_info` · `portfolios_videos.add_video_to_portfolio` · `portfolios_videos.add_video_to_portfolio_alt1` · `portfolios_videos.delete_video_from_portfolio` · `portfolios_videos.delete_video_from_portfolio_alt1` · `showcases_custom_showcase_logos.delete_showcase_logo` · `showcases_custom_showcase_thumbnails.delete_showcase_custom_thumbnail` · `showcases_essentials.add_folder_to_showcases` · `showcases_essentials.delete_showcase` · `showcases_essentials.delete_showcase_alt2` · `showcases_essentials.update_showcases` · `showcases_showcase_videos.add_video_to_showcase` · `showcases_showcase_videos.add_video_to_showcase_alt2` · `showcases_showcase_videos.remove_video_from_showcase` · `showcases_showcase_videos.remove_video_from_showcase_alt2` · `showcases_showcase_videos.replace_videos_in_showcase` · `showcases_showcase_videos.replace_videos_in_showcase_alt2` · `subscription_plans_essentials.get_plan` · `teams_essentials.backfill_team_custom_metadata` · `teams_essentials.get_team_custom_metadata_incomplete_videos` · `teams_members.get_team_information` · `teams_members.get_team_information2` · `teams_members.get_team_role_information` · `tutorial_essentials.developer_tutorial` · `users_followers.check_if_user_is_following` · `users_followers.check_if_user_is_following_alt1` · `users_followers.follow_user` · `users_followers.follow_user_alt1` · `users_followers.follow_users` · `users_followers.follow_users_alt1` · `users_followers.unfollow_user` · `users_followers.unfollow_user_alt1` · `users_lms.download_lms_package` · `users_pictures.delete_picture` · `users_pictures.delete_picture_alt1` · `users_watch_history.delete_from_watch_history` · `users_watch_history.delete_watch_history` · `videos_ai.ask_ai_question` · `videos_ai.edit_ai_texttrack` · `videos_ai.get_ai_languages` · `videos_ai.get_ai_metadata` · `videos_ai.get_ai_questions` · `videos_ai.get_ai_transcribe_status` · `videos_ai.get_ai_translate_dubbing_status` · `videos_ai.get_ai_translate_subtitles_status` · `videos_ai.start_ai_transcribe` · `videos_ai.start_ai_translate_dubbing` · `videos_ai.start_ai_translate_subtitles` · `videos_animated_thumbnails.delete_animated_thumbset` · `videos_chapters.delete_chapter` · `videos_chapters.delete_chapter_thumbnail` · `videos_credits.delete_video_credit` · `videos_embed_privacy.add_video_privacy_domain` · `videos_embed_privacy.delete_video_privacy_domain` · `videos_essentials.delete_video` · `videos_essentials.delete_videos` · `videos_essentials.delete_videos_alt1` · `videos_fragments.delete_fragments` · `videos_fragments.put_fragments` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback_alt1` · `videos_tags.delete_video_tag` · `videos_text_tracks.delete_text_track` · `videos_thumbnails.delete_video_thumbnail` · `videos_unlisted_videos.delete_video_privacy_user` · `videos_uploads.complete_streaming_upload` · `videos_versions.delete_audio_track` · `videos_versions.delete_video_version` · `videos_versions.get_audiotrack_downloads` · `videos_video_comments.delete_comment` · `watch_later_queue_essentials.add_video_to_watch_later` · `watch_later_queue_essentials.add_video_to_watch_later_alt1` · `watch_later_queue_essentials.delete_video_from_watch_later` · `watch_later_queue_essentials.delete_video_from_watch_later_alt1`. Their raw peers are `ApiResult[None, …]`, so `with_raw_response` is the only way to observe the status code. +4. **Required vs `UNSET`** for every model member the task sets, and the fact that `Optional[T]` here is `T | UnsetType` — **not** `typing.Optional`, so `None` is not a legal value for it. +5. **The `ApiError.error` union** for each operation in scope — there are **three** typed error bodies in this SDK, so the union is never uniform. Every union is ` | RawError`: + 1. `LegacyError` — 237 operations (`api_apps_webhooks.delete_webhook` · `authentication_extras_essentials.delete_token` · `authentication_extras_essentials.verify_token` · `categories_channels.get_category_channels` · `categories_essentials.get_category` · `categories_groups.get_category_groups` · `categories_videos.check_category_for_video` · `categories_videos.get_category_videos` · `categories_videos.get_video_categories` · `categories_videos.suggest_video_category` · `channels_categories.add_channel_categories` · `channels_categories.categorize_channel` · `channels_categories.delete_channel_category` · `channels_categories.get_channel_categories` · `channels_essentials.create_channel` · `channels_essentials.delete_channel` · `channels_essentials.edit_channel` · `channels_essentials.get_channels` · `channels_moderators.add_channel_moderator` · `channels_moderators.add_channel_moderators` · `channels_moderators.remove_channel_moderator` · `channels_moderators.remove_channel_moderators` · `channels_moderators.replace_channel_moderators` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel` · `channels_subscriptions_and_subscribers.check_if_user_subscribed_to_channel_alt1` · `channels_subscriptions_and_subscribers.get_channel_subscribers` · `channels_subscriptions_and_subscribers.subscribe_to_channel` · `channels_subscriptions_and_subscribers.subscribe_to_channel_alt1` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel` · `channels_subscriptions_and_subscribers.unsubscribe_from_channel_alt1` · `channels_tags.add_channel_tag` · `channels_tags.add_tags_to_channel` · `channels_tags.check_if_channel_has_tag` · `channels_tags.delete_tag_from_channel` · `channels_tags.get_channel_tags` · `channels_videos.add_video_to_channel` · `channels_videos.add_videos_to_channel` · `channels_videos.delete_video_from_channel` · `channels_videos.get_available_video_channels` · `channels_videos.get_channel_video` · `channels_videos.get_channel_videos` · `channels_videos.remove_videos_from_channel` · `embed_presets_custom_logos.create_custom_logo` · `embed_presets_custom_logos.create_custom_logo_alt1` · `embed_presets_custom_logos.delete_custom_logo` · `embed_presets_custom_logos.delete_custom_logo_alt1` · `embed_presets_custom_logos.get_custom_logo` · `embed_presets_custom_logos.get_custom_logo_alt1` · `embed_presets_custom_logos.get_custom_logos` · `embed_presets_custom_logos.get_custom_logos_alt1` · `embed_presets_essentials.delete_embed_preset` · `embed_presets_essentials.delete_embed_preset_alt1` · `embed_presets_essentials.edit_embed_preset` · `embed_presets_essentials.edit_embed_preset_alt1` · `embed_presets_folders.delete_folder_embed_preset` · `embed_presets_timeline_events.create_video_custom_logo` · `embed_presets_timeline_events.get_video_custom_logo` · `embed_presets_videos.delete_video_embed_preset` · `embed_presets_videos.get_video_embed_preset` · `groups_essentials.create_group` · `groups_essentials.delete_group` · `groups_subscriptions.join_group` · `groups_subscriptions.join_group_alt1` · `groups_subscriptions.leave_group` · `groups_subscriptions.leave_group_alt1` · `groups_users.check_if_user_joined_group` · `groups_users.check_if_user_joined_group_alt1` · `groups_users.get_group_members` · `groups_videos.add_video_to_group` · `groups_videos.delete_video_from_group` · `groups_videos.get_group_video` · `groups_videos.get_group_videos` · `likes_essentials.check_if_user_liked_video` · `likes_essentials.check_if_user_liked_video_alt1` · `likes_essentials.like_video` · `likes_essentials.like_video_alt1` · `likes_essentials.unlike_video` · `likes_essentials.unlike_video_alt1` · `on_demand_backgrounds.create_vod_background` · `on_demand_backgrounds.delete_vod_background` · `on_demand_backgrounds.edit_vod_background` · `on_demand_backgrounds.get_vod_background` · `on_demand_backgrounds.get_vod_backgrounds` · `on_demand_essentials.delete_vod_draft` · `on_demand_essentials.edit_vod` · `on_demand_essentials.get_user_vods` · `on_demand_essentials.get_user_vods_alt1` · `on_demand_essentials.get_vod` · `on_demand_genres.add_vod_genre` · `on_demand_genres.delete_vod_genre` · `on_demand_genres.get_genre_vod` · `on_demand_genres.get_vod_genre` · `on_demand_genres.get_vod_genre_by_ondemand_id` · `on_demand_genres.get_vod_genres_by_ondemand_id` · `on_demand_posters.add_vod_poster` · `on_demand_posters.get_vod_posters` · `on_demand_promotions.create_vod_promotion` · `on_demand_promotions.delete_vod_promotion` · `on_demand_promotions.get_vod_promotion` · `on_demand_promotions.get_vod_promotion_codes` · `on_demand_promotions.get_vod_promotions` · `on_demand_purchases_and_rentals.check_if_vod_was_purchased_alt1` · `on_demand_purchases_and_rentals.get_vod_purchases` · `on_demand_purchases_and_rentals.get_vod_purchases_alt1` · `on_demand_regions.add_vod_region` · `on_demand_regions.delete_vod_region` · `on_demand_regions.delete_vod_regions` · `on_demand_regions.get_region` · `on_demand_regions.get_vod_region` · `on_demand_regions.get_vod_regions` · `on_demand_regions.set_vod_regions` · `on_demand_seasons.get_vod_season` · `on_demand_seasons.get_vod_seasons` · `on_demand_videos.add_video_to_vod` · `on_demand_videos.delete_video_from_vod` · `portfolios_videos.add_video_to_portfolio` · `portfolios_videos.add_video_to_portfolio_alt1` · `portfolios_videos.delete_video_from_portfolio` · `portfolios_videos.delete_video_from_portfolio_alt1` · `showcases_custom_showcase_logos.create_showcase_logo` · `showcases_custom_showcase_logos.delete_showcase_logo` · `showcases_custom_showcase_logos.get_showcase_logo` · `showcases_custom_showcase_logos.get_showcase_logos` · `showcases_custom_showcase_logos.replace_showcase_logo` · `showcases_custom_showcase_thumbnails.create_showcase_custom_thumb` · `showcases_custom_showcase_thumbnails.delete_showcase_custom_thumbnail` · `showcases_custom_showcase_thumbnails.get_showcase_custom_thumbnail` · `showcases_custom_showcase_thumbnails.get_showcase_custom_thumbs` · `showcases_custom_showcase_thumbnails.replace_showcase_custom_thumb` · `showcases_essentials.copy_showcase` · `showcases_essentials.copy_showcase_alt2` · `showcases_essentials.create_showcase` · `showcases_essentials.create_showcase_alt1` · `showcases_essentials.delete_showcase` · `showcases_essentials.delete_showcase_alt2` · `showcases_essentials.edit_showcase` · `showcases_essentials.edit_showcase_alt2` · `showcases_essentials.get_showcase` · `showcases_essentials.get_showcase_alt2` · `showcases_essentials.get_showcases` · `showcases_essentials.get_showcases_alt1` · `showcases_showcase_videos.add_video_to_showcase` · `showcases_showcase_videos.add_video_to_showcase_alt2` · `showcases_showcase_videos.get_available_showcase_videos` · `showcases_showcase_videos.get_showcase_video` · `showcases_showcase_videos.get_showcase_video_alt2` · `showcases_showcase_videos.get_showcase_videos` · `showcases_showcase_videos.get_showcase_videos_alt2` · `showcases_showcase_videos.remove_video_from_showcase` · `showcases_showcase_videos.remove_video_from_showcase_alt2` · `showcases_showcase_videos.replace_videos_in_showcase` · `showcases_showcase_videos.replace_videos_in_showcase_alt2` · `teams_essentials.backfill_team_custom_metadata` · `teams_essentials.get_team_custom_metadata_incomplete_videos` · `teams_essentials.get_team_custom_metadata_settings` · `teams_essentials.update_custom_metadata_field_options` · `teams_essentials.upsert_custom_metadata_settings` · `users_followers.check_if_user_is_following` · `users_followers.check_if_user_is_following_alt1` · `users_followers.follow_user` · `users_followers.follow_user_alt1` · `users_search.search_users` · `users_watch_history.get_watch_history` · `videos_ai.ask_ai_question` · `videos_ai.edit_ai_texttrack` · `videos_ai.get_ai_metadata` · `videos_ai.get_ai_questions` · `videos_ai.get_ai_transcribe_status` · `videos_ai.get_ai_translate_dubbing_status` · `videos_ai.get_ai_translate_subtitles_status` · `videos_ai.start_ai_transcribe` · `videos_ai.start_ai_translate_dubbing` · `videos_ai.start_ai_translate_subtitles` · `videos_animated_thumbnails.create_animated_thumbset` · `videos_chapters.create_chapter` · `videos_chapters.create_chapter_thumbnail_or_upload_link` · `videos_chapters.create_unsaved_chapter_thumbnail_or_upload_link` · `videos_chapters.delete_chapter` · `videos_chapters.delete_chapter_thumbnail` · `videos_chapters.edit_chapter` · `videos_chapters.get_chapter` · `videos_chapters.get_chapter_thumbnail` · `videos_chapters.get_chapter_thumbnails` · `videos_chapters.get_chapters` · `videos_chapters.get_unsaved_chapter_thumbnail` · `videos_chapters.set_chapter_thumbnail_active` · `videos_credits.add_video_credit` · `videos_credits.add_video_credit_alt1` · `videos_credits.delete_video_credit` · `videos_credits.edit_video_credit` · `videos_credits.get_video_credit` · `videos_embed_privacy.add_video_privacy_domain` · `videos_embed_privacy.delete_video_privacy_domain` · `videos_embed_privacy.get_video_privacy_domains` · `videos_essentials.check_if_user_owns_video` · `videos_essentials.check_if_user_owns_video_alt1` · `videos_essentials.copy_video` · `videos_essentials.copy_video_alt2` · `videos_essentials.delete_video` · `videos_essentials.edit_video` · `videos_essentials.get_video` · `videos_essentials.search_videos` · `videos_essentials.update_video_custom_metadata` · `videos_recommendations.get_related_videos` · `videos_tags.add_video_tag` · `videos_tags.add_video_tags` · `videos_tags.check_video_for_tag` · `videos_tags.delete_video_tag` · `videos_tags.get_videos_with_tag` · `videos_text_tracks.create_text_track` · `videos_text_tracks.create_text_track_alt1` · `videos_text_tracks.delete_text_track` · `videos_text_tracks.edit_text_track` · `videos_text_tracks.get_text_track` · `videos_text_tracks.get_text_tracks` · `videos_text_tracks.get_text_tracks_alt1` · `videos_text_tracks.get_text_tracks_alt2` · `videos_text_tracks.get_text_tracks_alt3` · `videos_unlisted_videos.add_video_privacy_user` · `videos_unlisted_videos.delete_video_privacy_user` · `videos_unlisted_videos.get_video_privacy_users` · `videos_unlisted_videos.get_video_privacy_users_alt1` · `videos_versions.create_audio_track` · `videos_versions.delete_audio_track` · `videos_versions.delete_video_version` · `videos_versions.edit_audio_track` · `videos_versions.edit_video_version` · `videos_versions.get_audio_track` · `videos_versions.get_audio_tracks` · `videos_versions.get_video_version` · `videos_versions.get_video_versions` · `videos_versions.get_video_versions_alt1` · `videos_video_comments.delete_comment` · `videos_video_comments.get_comment` · `videos_video_comments.get_comment_replies` · `watch_later_queue_essentials.check_watch_later_queue` · `watch_later_queue_essentials.check_watch_later_queue_alt1`); distinguishing members `error` + 2. `Error` — 218 operations (`api_apps_webhooks.add_webhook` · `api_apps_webhooks.get_webhook` · `api_apps_webhooks.update_webhook` · `authentication_extras_authenticate.client_auth` · `categories_users.get_category_subscriptions` · `categories_users.get_category_subscriptions_alt1` · `channels_categories.add_channel_categories` · `channels_categories.categorize_channel` · `channels_categories.delete_channel_category` · `channels_essentials.get_channel_subscriptions` · `channels_essentials.get_channel_subscriptions_alt1` · `channels_essentials.get_channels` · `channels_moderators.add_channel_moderators` · `channels_moderators.replace_channel_moderators` · `channels_private_channel_members.delete_channel_privacy_user` · `channels_private_channel_members.get_channel_privacy_users` · `channels_private_channel_members.set_channel_privacy_user` · `channels_private_channel_members.set_channel_privacy_users` · `channels_tags.add_channel_tag` · `channels_tags.add_tags_to_channel` · `channels_tags.check_if_channel_has_tag` · `channels_tags.delete_tag_from_channel` · `folders_essentials.create_project` · `folders_essentials.create_project_alt1` · `folders_essentials.delete_project` · `folders_essentials.delete_project_alt1` · `folders_essentials.edit_project` · `folders_essentials.edit_project_alt1` · `folders_essentials.get_private_to_me_folder` · `folders_essentials.get_project` · `folders_essentials.get_project_alt1` · `folders_essentials.get_projects` · `folders_essentials.get_projects_alt1` · `folders_items.delete_folder_items_alt1` · `folders_videos.add_video_to_project` · `folders_videos.add_video_to_project_alt1` · `folders_videos.add_videos_to_project` · `folders_videos.add_videos_to_project_alt1` · `folders_videos.get_project_videos` · `folders_videos.get_project_videos_alt1` · `folders_videos.remove_video_from_project` · `folders_videos.remove_video_from_project_alt1` · `folders_videos.remove_videos_from_project` · `folders_videos.remove_videos_from_project_alt1` · `groups_essentials.create_group` · `groups_essentials.delete_group` · `groups_essentials.get_group` · `groups_essentials.get_groups` · `groups_subscriptions.join_group` · `groups_subscriptions.join_group_alt1` · `groups_subscriptions.leave_group` · `groups_subscriptions.leave_group_alt1` · `groups_users.get_user_groups` · `groups_users.get_user_groups_alt1` · `groups_videos.add_video_to_group` · `groups_videos.delete_video_from_group` · `groups_videos.get_available_video_groups` · `likes_essentials.get_likes` · `likes_essentials.get_likes_alt1` · `likes_essentials.get_vod_likes` · `live_analytics.export_vpaas_viewer_analytics` · `live_audio_tracks.get_event_audio_tracks` · `live_audio_tracks.update_event_audio_tracks` · `live_embed_privacy.get_live_event_whitelist` · `live_embed_privacy.get_live_event_whitelist_alt1` · `live_embed_privacy.get_live_event_whitelist_alt2` · `live_embed_privacy.set_live_event_whitelist` · `live_embed_privacy.set_live_event_whitelist_alt1` · `live_embed_privacy.set_live_event_whitelist_alt2` · `live_essentials.create_live_event` · `live_essentials.create_live_event_alt1` · `live_essentials.create_live_event_alt2` · `live_essentials.delete_live_event` · `live_essentials.delete_live_event_alt1` · `live_essentials.delete_live_event_alt2` · `live_essentials.delete_live_events` · `live_essentials.delete_live_events_alt1` · `live_essentials.delete_live_events_alt2` · `live_essentials.get_live_event` · `live_essentials.get_live_event_alt1` · `live_essentials.get_live_event_alt2` · `live_essentials.update_live_event` · `live_essentials.update_live_event_alt1` · `live_essentials.update_live_event_alt2` · `live_event_m3_u8_playback.get_live_event_m3u8_playback` · `live_event_m3_u8_playback.get_live_event_m3u8_playback_alt1` · `live_event_activation.activate_live_event` · `live_event_activation.activate_live_event_alt1` · `live_event_activation.activate_live_event_alt2` · `live_event_automated_closed_captions.edit_live_event_auto_cc` · `live_event_automated_closed_captions.edit_live_event_auto_cc_alt1` · `live_event_automated_closed_captions.edit_live_event_auto_cc_alt2` · `live_event_destinations.create_live_event_destination` · `live_event_destinations.create_live_event_destination_alt1` · `live_event_destinations.create_ott_destination` · `live_event_destinations.delete_live_event_destination` · `live_event_destinations.delete_ott_destination` · `live_event_destinations.get_available_destinations` · `live_event_destinations.get_available_destinations_alt1` · `live_event_destinations.get_live_event_destination` · `live_event_destinations.get_live_event_destinations` · `live_event_destinations.get_live_event_destinations_alt1` · `live_event_destinations.get_ott_destinations` · `live_event_destinations.update_live_event_destination` · `live_event_end.end_live_event` · `live_event_end.end_live_event_alt1` · `live_event_end.end_live_event_alt2` · `live_event_low_latency.toggle_rle_low_latency` · `live_event_low_latency.toggle_rle_low_latency_alt1` · `live_event_low_latency.toggle_rle_low_latency_alt2` · `live_event_thumbnails.create_live_event_thumbnail` · `live_event_thumbnails.create_live_event_thumbnail_alt1` · `live_event_thumbnails.create_live_event_thumbnail_alt2` · `live_event_thumbnails.delete_live_event_thumbnail` · `live_event_thumbnails.delete_live_event_thumbnail_alt1` · `live_event_thumbnails.delete_live_event_thumbnail_alt2` · `live_event_thumbnails.edit_live_event_thumbnail` · `live_event_thumbnails.edit_live_event_thumbnail_alt1` · `live_event_thumbnails.edit_live_event_thumbnail_alt2` · `live_event_thumbnails.get_live_event_thumbnail` · `live_event_thumbnails.get_live_event_thumbnail_alt1` · `live_event_thumbnails.get_live_event_thumbnail_alt2` · `live_event_thumbnails.get_live_event_thumbnails` · `live_event_thumbnails.get_live_event_thumbnails_alt1` · `live_event_thumbnails.get_live_event_thumbnails_alt2` · `live_event_videos.add_videos_to_live_event` · `live_event_videos.add_videos_to_live_event_alt1` · `live_event_videos.add_videos_to_live_event_alt2` · `live_event_videos.get_live_event_video` · `live_event_videos.get_live_event_video_alt1` · `live_event_videos.get_live_event_video_alt2` · `live_event_videos.get_live_event_videos` · `live_event_videos.get_live_event_videos_alt1` · `live_event_videos.get_live_event_videos_alt2` · `live_event_videos.remove_videos_from_live_event` · `live_event_videos.remove_videos_from_live_event_alt1` · `live_event_videos.remove_videos_from_live_event_alt2` · `live_graphics.add_composer_video_graphic_item` · `live_graphics.delete_composer_video_graphic_item` · `live_graphics.get_composer_video_graphic_items` · `live_scenes.attach_graphic_item_to_scene` · `live_scenes.create_composer_scene` · `live_scenes.get_composer_scenes` · `live_scenes.reset_composer_scenes` · `live_scenes.update_composer_scene` · `on_demand_genres.get_genre_vods` · `on_demand_genres.get_vod_genres` · `payments_essentials.delete_payment_method` · `payments_essentials.get_payment_method_info` · `payments_essentials.list_payment_methods` · `search_federated.federated_search_user_items` · `search_federated.federated_search_user_items_alt1` · `showcases_custom_showcase_thumbnails.get_showcase_custom_thumbs` · `showcases_essentials.add_folder_to_showcases` · `showcases_showcase_videos.get_available_showcase_videos` · `showcases_showcase_videos.get_available_video_showcases` · `showcases_showcase_videos.set_video_as_showcase_featured` · `showcases_showcase_videos.set_video_as_showcase_featured_alt2` · `showcases_showcase_videos.set_video_as_showcase_thumbnail` · `showcases_showcase_videos.set_video_as_showcase_thumbnail_alt2` · `teams_essentials.backfill_team_custom_metadata` · `teams_essentials.get_team_custom_metadata_incomplete_videos` · `teams_essentials.get_team_custom_metadata_settings` · `teams_essentials.update_custom_metadata_field_options` · `teams_essentials.upsert_custom_metadata_settings` · `users_essentials.edit_user` · `users_essentials.edit_user_alt1` · `users_feeds.get_feed` · `users_feeds.get_feed_alt1` · `users_followers.follow_users` · `users_followers.follow_users_alt1` · `users_search.search_users` · `videos_ai.ask_ai_question` · `videos_ai.edit_ai_texttrack` · `videos_ai.get_ai_languages` · `videos_ai.get_ai_metadata` · `videos_ai.get_ai_questions` · `videos_ai.get_ai_transcribe_status` · `videos_ai.get_ai_translate_dubbing_status` · `videos_ai.get_ai_translate_subtitles_status` · `videos_ai.start_ai_transcribe` · `videos_ai.start_ai_translate_dubbing` · `videos_ai.start_ai_translate_subtitles` · `videos_animated_thumbnails.create_animated_thumbset` · `videos_animated_thumbnails.delete_animated_thumbset` · `videos_animated_thumbnails.get_all_animated_thumbset` · `videos_animated_thumbnails.get_animated_thumbset` · `videos_animated_thumbnails.get_animated_thumbset_status` · `videos_chapters.edit_chapter` · `videos_essentials.delete_videos` · `videos_essentials.delete_videos_alt1` · `videos_essentials.get_appearances` · `videos_essentials.get_appearances_alt1` · `videos_essentials.search_videos` · `videos_essentials.update_video_custom_metadata` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback` · `videos_live_m3_u8_playback.get_one_time_event_m3u8_playback_alt1` · `videos_moderation.get_video_reporting_reasons` · `videos_nondestructive_trimming.clip_trim` · `videos_recommendations.get_related_videos` · `videos_showcases.add_or_remove_multiple_albums` · `videos_showcases.get_video_albums` · `videos_text_tracks.delete_text_track` · `videos_text_tracks.get_text_track` · `videos_transcripts.get_transcript` · `videos_transcripts.get_transcript_metadata` · `videos_uploads.complete_streaming_upload` · `videos_uploads.upload_video` · `videos_uploads.upload_video_alt1` · `videos_versions.create_video_version` · `videos_versions.edit_video_version` · `videos_versions.get_video_versions` · `videos_versions.get_video_versions_alt1` · `videos_video_comments.create_comment` · `videos_video_comments.create_comment_alt1` · `videos_video_comments.create_comment_reply` · `videos_video_comments.delete_comment` · `videos_video_comments.edit_comment`); distinguishing members `developer_message` · `error` · `error_code` · `link` + 3. `AuthError` — 2 operations (`authentication_extras_convert.convert_access_token` · `authentication_extras_exchange.exchange_auth_code`); distinguishing members `error` · `error_description` + 4. *(none)* — `api_apps_webhooks.get_webhooks` · `api_information_essentials.get_endpoints` · `categories_essentials.get_categories` · `categories_users.check_if_user_subscribed_to_category` · `categories_users.check_if_user_subscribed_to_category_alt1` · `categories_users.subscribe_to_category` · `categories_users.subscribe_to_category_alt1` · `categories_users.unsubscribe_from_category` · `categories_users.unsubscribe_from_category_alt1` · `channels_essentials.get_channel` · `channels_moderators.get_channel_moderator` · `channels_moderators.get_channel_moderators` · `embed_presets_essentials.create_embed_presets` · `embed_presets_essentials.create_embed_presets_alt1` · `embed_presets_essentials.get_embed_preset` · `embed_presets_essentials.get_embed_preset_alt1` · `embed_presets_essentials.get_embed_presets` · `embed_presets_essentials.get_embed_presets_alt1` · `embed_presets_videos.add_video_embed_preset` · `embed_presets_videos.get_embed_preset_videos` · `embed_presets_videos.get_embed_preset_videos_alt1` · `folders_items.get_folder_items` · `folders_items.get_folder_items2` · `likes_essentials.get_video_likes` · `likes_essentials.get_video_likes_alt1` · `live_essentials.get_live_event_occurrences` · `live_essentials.get_live_events` · `live_essentials.get_live_events_alt1` · `live_essentials.get_live_events_alt2` · `live_event_sessions.get_live_clip_ingest_status` · `live_event_sessions.get_live_ingest_status` · `on_demand_essentials.create_vod` · `on_demand_essentials.create_vod_alt1` · `on_demand_posters.edit_vod_poster` · `on_demand_posters.get_vod_poster` · `on_demand_regions.get_regions` · `on_demand_seasons.get_vod_season_videos` · `on_demand_videos.get_vod_video` · `on_demand_videos.get_vod_videos` · `payments_essentials.get_subscription_info` · `portfolios_essentials.get_portfolio` · `portfolios_essentials.get_portfolio_alt1` · `portfolios_essentials.get_portfolios` · `portfolios_essentials.get_portfolios_alt1` · `portfolios_videos.get_portfolio_video` · `portfolios_videos.get_portfolio_video_alt1` · `portfolios_videos.get_portfolio_videos` · `portfolios_videos.get_portfolio_videos_alt1` · `showcases_essentials.update_showcases` · `subscription_plans_essentials.get_plan` · `tags_essentials.get_tag` · `teams_members.get_team_information` · `teams_members.get_team_information2` · `teams_members.get_team_role_information` · `tutorial_essentials.developer_tutorial` · `users_analytics.get_user_analytics` · `users_analytics.get_user_analytics_alt1` · `users_essentials.get_user` · `users_essentials.get_user_alt1` · `users_followers.get_followers` · `users_followers.get_followers_alt1` · `users_followers.get_user_following` · `users_followers.get_user_following_alt1` · `users_followers.unfollow_user` · `users_followers.unfollow_user_alt1` · `users_lms.download_lms_package` · `users_pictures.create_picture` · `users_pictures.create_picture_alt1` · `users_pictures.delete_picture` · `users_pictures.delete_picture_alt1` · `users_pictures.edit_picture` · `users_pictures.edit_picture_alt1` · `users_pictures.get_picture` · `users_pictures.get_picture_alt1` · `users_pictures.get_pictures` · `users_pictures.get_pictures_alt1` · `users_watch_history.delete_from_watch_history` · `users_watch_history.delete_watch_history` · `videos_content_ratings.get_content_ratings` · `videos_creative_commons.get_cc_licenses` · `videos_credits.available_users` · `videos_credits.get_video_credits` · `videos_credits.get_video_credits_alt1` · `videos_essentials.get_videos` · `videos_essentials.get_videos_alt1` · `videos_fragments.delete_fragments` · `videos_fragments.get_fragments` · `videos_fragments.put_fragments` · `videos_languages.get_languages` · `videos_tags.get_video_tags` · `videos_thumbnails.create_video_thumbnail` · `videos_thumbnails.create_video_thumbnail_alt1` · `videos_thumbnails.delete_video_thumbnail` · `videos_thumbnails.edit_video_thumbnail` · `videos_thumbnails.get_video_thumbnail` · `videos_thumbnails.get_video_thumbnails` · `videos_thumbnails.get_video_thumbnails_alt1` · `videos_unlisted_videos.add_video_privacy_users` · `videos_unlisted_videos.add_video_privacy_users_alt1` · `videos_uploads.get_upload_attempt` · `videos_versions.get_audiotrack_downloads` · `videos_versions.get_version_thumbnail` · `videos_video_comments.get_comments` · `videos_video_comments.get_comments_alt1` · `watch_later_queue_essentials.add_video_to_watch_later` · `watch_later_queue_essentials.add_video_to_watch_later_alt1` · `watch_later_queue_essentials.delete_video_from_watch_later` · `watch_later_queue_essentials.delete_video_from_watch_later_alt1` · `watch_later_queue_essentials.get_watch_later_queue` · `watch_later_queue_essentials.get_watch_later_queue_alt1`: no typed arm, so `.error` is always `RawError` + 5. So `isinstance(e.error, LegacyError)` matches only 237 of 520 operations. +6. **That a decode failure raises `ValidationError`/`ValueError`, not `ApiError`, in both response modes** — `core/raw_client.py` states this in `execute`'s own docstring. **And that the 2xx path declares at least one required member on 212 return types, so a truncated body fails to decode there and passes silently everywhere else.** Any sheet row for a call whose result is used must name the members the implementer has to assert on. +7. **That retrying is on by default** — `retry_options` on the client tunes it, `max_retries` set to `0` turns it off, and `request_options` overrides it per call — so the sheet says whether this integration keeps, tunes or disables it, and for which methods, since a write is repeated only when the policy names its method. +8. **What limits a whole call.** `timeout` limits each wait, not the call, so a reply whose pieces each arrive within it is never cut off. In async code, state the budget and the one place `asyncio.wait_for` enforces it for every call, and what the boundary returns for its `asyncio.TimeoutError`; in sync code, say there is none. +9. **Which host the `base_url` selects**, because omitting it is silently the default. +10. A **REQUIRED READING** block naming the `python-*` companions that govern the steps, with `MUST load` pointers. +