Official SDKs and generation
Pulp Engine maintains these first-class clients:
| Language | Package | Implementation | Availability |
|---|---|---|---|
| TypeScript | @pulp-engine/sdk |
Hand-written | npm release workflow |
| Python | pulp-engine |
Hand-written, sync and async | PyPI release workflow |
| .NET | PulpEngine.Sdk |
Generated HttpClient APIs plus a committed facade | First publication pending registry onboarding |
| Java | dev.pulpengine:pulpengine-sdk |
Generated OkHttp/Gson APIs plus a committed facade | First publication pending registry onboarding |
| Go | github.com/TroyCoderBoy/pulpengine-go |
Generated APIs plus a committed facade | First publication pending public mirror onboarding |
| PHP | pulpengine/pulpengine-php |
Generated Guzzle 7 APIs plus a committed facade | First publication pending public mirror/Packagist onboarding |
The four additional clients are MIT-licensed packages. Their source, tests and release tooling are maintained here; the server and renderers retain their commercial licence. First publication and the first GitHub Actions release run are tracked separately from local verification.
Generate
The canonical contract is openapi.json, also served at /docs/json when documentation is enabled. It contains named JSON request and response models, stable operation IDs and separate binary content types. Extract after changing routes or the root version:
pnpm extract-openapi
pnpm check:openapi-components
pnpm sdk:generate # all four additional clients
pnpm sdk:generate --lang dotnet # dotnet | java | go | php
pnpm sdk:generate --lang dotnet --check # regenerate separately and compare
Generation uses OpenAPI Generator 7.26.0, pinned in openapitools.json, with the pinned npm wrapper. It needs Java 11+ or Docker. Set PULP_ENGINE_SDK_GEN=docker to force the matching Docker image. Output is confined to each package’s ignored generated/ directory. Commit the facades, tests and configuration; generated clients are assembled at release time. Versions come from the root package.json.
The generator normalizes LF endings, hides generation timestamps and uses a fixed .NET project GUID. A narrowly scoped, tested C# post-pass restores missing overloads in the pinned generator’s anyOf models. Existing model validation remains intact. Conditional serialization preserves unset versus explicitly assigned values; a pinned template repair restores constructor initialization that this generator version omits. Arbitrary JSON/data retains its original key casing. Regeneration checks ignore compiler output directories.
| Client | Build targets | Committed source |
|---|---|---|
| .NET | netstandard2.0, net8.0, net10.0; nullable references |
sdk-dotnet |
| Java | Java 11+, OkHttp/Gson | sdk-java |
| Go | Go 1.23+ | sdk-go |
| PHP | PHP 8.1+, Guzzle 7 | sdk-php |
Verify locally
pnpm sdk:verify # generate, build the API image, verify all SDKs
pnpm sdk:verify --lang java
pnpm sdk:verify --image pulp-engine:local # reuse an image built from this checkout
pnpm sdk:verify --skip-generate --unit-only
Docker verification copies sources into isolated build containers, so native language tools are optional. It creates its own API container, random administrator key and network, then removes those containers and that network. Results and packages are written to ignored .sdk-artifacts/. The smoke tests check the API/SDK version, a streamed PDF prefix, a completed unified batch job with an HTTPS webhook configured, and a typed missing-template error with a request ID. These tests verify job completion; they do not assert that example.com accepts the webhook.
The shared signature vectors in sdk-shared are consumed by all six SDKs and the server signer. Fake transports verify retries, response streaming, empty sinks on HTTP errors, cancellation and polling timeouts.
Common facade behavior
Each facade exposes configured generated APIs and sends User-Agent: pulpengine-<language>/<version>. Choose exactly one API key or editor token. Binary helpers call /render/{pdf,docx,xlsx,pptx,csv,html} directly and copy bytes incrementally into a caller-owned sink. A non-success HTTP status raises a typed error before the sink receives bytes. Error codes remain strings, including future codes. A later transport or sink failure can leave partial output; callers own its cleanup.
waitForJob uses /render/jobs/{id}, with a default two-second poll and five-minute timeout, and honors Retry-After. Cancellation follows each language’s conventions. PHP accepts a cancellation callback, checked before requests, during retry waits and between reads; blocking I/O is bounded by its configured timeout.
Retries follow FA-25 and the TypeScript/Python SDKs: three retries, 500 ms base, 30 s maximum and full jitter for 408, 429, 502, 503, 504 and connection failures. Safe HTTP methods can retry. POSTs require both an idempotency key and a known idempotent/pure API path; those paths receive a stable automatic key. Arbitrary POSTs are not retried. A server-requested delay above the policy maximum returns the response rather than retrying early. Streaming failures after response consumption begins are never retried.
Webhook verification computes HMAC-SHA256 over timestamp + "." + rawBody, compares signatures in constant time, requires sha256=, accepts any matching comma-separated rotation signature, and rejects timestamps beyond the default 300-second tolerance. Verify before parsing the body and deduplicate event/job IDs in your receiver.
Release
publish-sdks-generated.yml runs only on release calls or manual dispatch, generates once, then builds/verifies/packages each language. Dry runs build the checked-out API image and skip publication. Normal releases reuse the anonymously verified release image. Publication remains disabled until registry onboarding is complete and SDK_GENERATED_PUBLISH_ENABLED=true is configured.
Read each package’s RELEASING.md before enabling publication. NuGet supports trusted publishing or an API key; Java uses a signed Central Portal bundle; Go and PHP use public mirror tags. Published versions are immutable. Go major version 2 will require a /v2 module/import-path change before generation and release.
For other languages, generate your own client with OpenAPI Generator using the same spec. Such clients receive the low-level API surface; the maintained streaming, retry and webhook facades above are available only in the six first-class languages.
The isolated smoke API uses HARDEN_PRODUCTION=false, matching the HTTP evaluation setup. This is confined to the temporary test container; the production image retains its default hardening. Startup failures include container logs before cleanup.