Two ways to call a raw API from Pulumi
Two ways to consume raw APIs from Pulumi when no first-class provider exists: a Dynamic Provider (full lifecycle) and @pulumi/command (fire-and-forget shells).
This example lives in the pulumi/examples repository. Check out just this directory to use it:
git clone --filter=blob:none --sparse https://github.com/pulumi/examples pulumi-examplesgit -C pulumi-examples sparse-checkout set ts-raw-api-providerscd pulumi-examples/ts-raw-api-providersThere are two paths when the API you want to use has no Pulumi provider yet, and you don’t want to wait around for one. This example does both, against real public endpoints, so you can see how they actually behave.
pulumi.dynamic.Resource— write a custom resource withcreate,read,update,delete, anddiff. Pulumi stores it in state, diffs it like any other resource, and calls the right method onup/refresh/destroy. This is what you want when the thing has an identity and a lifecycle.@pulumi/command— shell out a command on create, optionally another on delete. There’s no state of the remote object, just the command itself. Fine for one-shot effects (a curl call, a CLI invocation).
Both patterns hit real public HTTP endpoints. No local files, no fakes.
| Pattern | Endpoint | Verbs used |
|---|---|---|
Dynamic provider (HttpbinEcho) | https://httpbin.org/anything | POST, GET, PUT, DELETE |
command.local.Command (zen) | https://api.github.com/zen | GET via curl |
Files#
| File | What it does |
|---|---|
httpbinEcho.ts | The dynamic provider. HttpbinEcho class with the CRUD methods, each backed by fetch(). |
index.ts | Uses HttpbinEcho (pattern 1) and command.local.Command (pattern 2). |
Prerequisites#
Deploying the example#
-
Create a new stack:
Terminal window pulumi stack init dev -
Install dependencies:
Terminal window npm install -
Deploy the stack:
Terminal window pulumi up --yes -
View the stack outputs:
Terminal window pulumi stack outputYou should see
echoUrl,echoEtag,echoLastMethod=POST,echoTraceId,echoedBody, andgithubZen. -
Change an input and redeploy to watch the provider call
update()(PUT) instead ofcreate()(POST):Terminal window pulumi config set message "edited via pulumi up"pulumi up --yespulumi stack outputechoLastMethodis nowPUT,etagandtraceIdhave changed, andechoedBody.messageis updated.
Cleaning up#
Remove the resources and the stack:
pulumi destroy --yespulumi stack rm --yespulumi destroy calls the provider’s delete() method → DELETE https://httpbin.org/anything.
How the dynamic provider works#
The five methods on provider get serialized by Pulumi, closures and all,
and run in a separate Node process. Two things to know:
- Method bodies have to be self-contained. We
await import("node:crypto")inside each method (and use Node 18+‘s built-infetch) so nothing top-level is captured by the closure serializer. - The
idreturned fromcreateis the resource’s identity for every laterread/update/deletecall. Here we use httpbin’sX-Amzn-Trace-Idas a stand-in. diffdecides between an in-place update (changes: true) and a replace (replaces: ["someProp"]).
Pointing this at a real REST API#
Swap the four fetch calls in httpbinEcho.ts for calls against your real
service. The CRUD shape stays the same:
create → POST /resource → returns body with idread → GET /resource/{id} → returns bodyupdate → PUT /resource/{id} → returns bodydelete → DELETE /resource/{id} → 204Which one do I want?#
| Need | Use |
|---|---|
| Real lifecycle, diffs, refresh, outputs tracked in state | Dynamic provider |
| Fire a curl/CLI on create, optionally clean up on delete | @pulumi/command |
| Reuse across projects or languages | Build a real provider via the Terraform bridge or provider-boilerplate |