Register a provisioner
Goal
For platform teams. In this task you tell the control plane about a provisioner, so that plan and apply can call it for the types that it handles. You also learn how to change a registration and how to remove it.
Before you start
- Sign in with the role
operator. Every registry call needs it. A person with only the roleowner:<team>gets403. - Deploy the provisioner, and make sure that it answers
POST /plan,POST /applyandPOST /inspect. What a provisioner is lists the calls. - Know the address that the control plane uses to reach it. The control plane must be allowed to call that address. Allow the address of your provisioner explains the setting.
- Know the category and the type of each pair that the provisioner handles. No other provisioner may handle the same pair.
- Decide if the provisioner accepts slow work. If it does, you set a longest wait. Support slow work in a provisioner explains it.
Steps
Register in the dashboard
- Sign in to the Platform dashboard with the role
operator. - Open Provisioners, and choose Register a provisioner.
- Fill in the form:
| Field | What to write |
|---|---|
| Name | Lower case letters and digits, with single hyphens between them, for example postgres-provisioner. Up to 255 characters. |
| URL | The address of the provisioner, with no user name and no password. |
| Types | One row for each pair. Choose the category (the form calls it Kind), and write the type. Use Add a type for more rows. |
| Async limit | The longest wait, as an ISO 8601 duration such as PT10M. Leave it empty for a provisioner that answers at once. |
- Choose Save. The dashboard shows the page of the new registration. If a field is wrong, an error summary lists each problem and moves the focus to it.
Register with the API
The registry is the resource /provisioners. The body is JSON. This example uses curl with bash. First read your token into a variable without showing it:
read -rs JOOMIZ_TOKEN
export JOOMIZ_TOKEN
Paste the token and press Enter. The curl option --variable '%JOOMIZ_TOKEN' reads the variable from the environment, and --expand-header puts it into the header. So the token is not on the command line. This needs curl 8.3 or later. Then send the registration. Write the address of your provisioner in url:
curl -s -w '\nHTTP %{http_code}\n' --variable '%JOOMIZ_TOKEN' --expand-header 'Authorization: Bearer {{JOOMIZ_TOKEN}}' -X POST "$JOOMIZ_SERVER_URL/provisioners" -H 'Content-Type: application/json' -d '{"name": "postgres-provisioner", "url": "https://postgres-provisioner.example.com", "types": [{"kind": "managed-resource", "type": "postgres"}]}'
The control plane answers 201 with the registration. The ID is made by the control plane:
{"id":"d7c1067f-0a27-4e0f-bab2-98e8eefa0d99","name":"postgres-provisioner","url":"https://postgres-provisioner.example.com","types":[{"kind":"managed-resource","type":"postgres"}],"createdAt":"2026-10-28T17:38:31.807Z","updatedAt":"2026-10-28T17:38:31.807Z","maxOperationDuration":null}
HTTP 201
The outputs on this page show example addresses in place of the addresses of the control plane and of the provisioner. The rules for the body are:
| Field | Rule |
|---|---|
name | Required. Lower case letters and digits, joined by single hyphens. At most 255 characters. |
url | Required. An http or https address with a host, at most 2048 characters, with no user name and no password. |
types | Required. From 1 to 50 pairs. Each pair has a kind and a type. A pair must not appear twice. |
maxOperationDuration | Optional. An ISO 8601 duration, more than zero and at most PT30M. Without it, the provisioner answers at once. |
The category (kind) is one of target, tool, deployment, managed-resource and consumed-resource.
The control plane does not check that a type exists in the catalog when you register it. A type that is not in the catalog never receives a call.
Check
List the registrations. The answer holds every provisioner, with its types:
curl -s -w '\nHTTP %{http_code}\n' --variable '%JOOMIZ_TOKEN' --expand-header 'Authorization: Bearer {{JOOMIZ_TOKEN}}' "$JOOMIZ_SERVER_URL/provisioners"
{"items":[{"id":"d7c1067f-0a27-4e0f-bab2-98e8eefa0d99","name":"postgres-provisioner","url":"https://postgres-provisioner.example.com","types":[{"kind":"managed-resource","type":"postgres"}],"createdAt":"2026-10-28T17:38:31.807Z","updatedAt":"2026-10-28T17:38:31.807Z","maxOperationDuration":null}]}
HTTP 200
Read one registration with GET /provisioners/<id>. Replace <id> with the id of the registration. In the dashboard, the page Provisioners lists the same registrations, and you can filter them by name.
The audit trail records each change. An operator reads it with joomiz audit. The actions are provisioner.registered, provisioner.updated and provisioner.deregistered.
Change a registration
A change replaces the whole registration. Send PUT /provisioners/<id> with the complete body, or choose Edit in the dashboard. A field that you leave out is not kept. For example, a body without maxOperationDuration withdraws the support for slow work.
The answer to a change that sets a longest wait of 10 minutes shows the new value and the new time of the update:
{"id":"d7c1067f-0a27-4e0f-bab2-98e8eefa0d99","name":"postgres-provisioner","url":"https://postgres-provisioner.example.com","types":[{"kind":"managed-resource","type":"postgres"}],"createdAt":"2026-10-28T17:38:31.807Z","updatedAt":"2026-10-28T17:38:31.881Z","maxOperationDuration":"PT10M"}
HTTP 200
The pairs of the new body must be free. A pair that another provisioner handles is refused.
Remove a provisioner
You remove (deregister) a provisioner with DELETE /provisioners/<id>. The answer is 204 with no body. In the dashboard, choose Deregister on the page of the provisioner. The dialog lists the types, and it asks you to type the exact name of the provisioner.
HTTP 204
After the removal, every pair of the provisioner is free for a new registration. plan and apply for those types fail until another provisioner handles them. The control plane removes the registration even if applications use those types. Check your applications before you remove a provisioner.
A request for a registration that does not exist answers 404:
{"detail":"provisioner 'd7c1067f-0a27-4e0f-bab2-98e8eefa0d99' is not registered","status":404,"title":"Provisioner not found"}
HTTP 404
Troubleshooting
| Answer | Cause | Fix |
|---|---|---|
401 | No token, or the token is not valid. | Sign in again, and read a new token. |
403 and Operator role required | Your sign-in has no operator role. | Ask for the role. |
400 and provisioner name is invalid | The name breaks the rule. | Use lower case letters, digits and single hyphens. |
400 and must be a syntactically valid absolute URL | The address is not http or https, or it has no host. | Write a complete https:// address. |
400 and must not embed credentials | The address has a user name or a password. | Remove them from the address. |
400 and points to an address that is not allowed | The control plane may not call that address. | Allow the network. Allow the address of your provisioner shows how. |
400 and host could not be resolved | The name does not resolve. | Check the name and the DNS of the control plane. |
400 and at least one {kind, type} pair is required | The list types is empty. | Name at least one pair. |
400 and at most 50 {kind, type} pairs may be claimed | The list types has more than 50 pairs. | Register the other pairs with a second provisioner. |
400 and kind ... is not supported | The kind is not one of the five. | Use a kind from the list above. |
400 and listed more than once | A pair appears twice in types. | Write each pair once. |
400 and maxOperationDuration | The duration is not valid, is zero, or is over PT30M. | Write a duration such as PT10M, or leave it out. |
409 and already claimed by another provisioner | Another provisioner handles the pair. | Remove that registration first, or choose another type. |
404 | No registration has this ID. | Check the ID in the list. |
This is the answer for a pair that another provisioner handles:
{"detail":"already claimed by another provisioner: managed-resource/postgres","status":409,"title":"Provisioned type already claimed"}
HTTP 409
The dashboard does not show the text of an answer. It shows a short text of its own. For a refused value, it says that the control plane did not accept the values. It asks you to check the name, the URL, the types and the async limit. For a pair that another provisioner handles, it says that a type is already claimed. To read the exact reason, send the same registration with the API.
Next
Write a provisioner of your own in Build a provisioner with the starter.