Enterprise deal templates
A template is a named milestone plan. The dashboard applies it on the New deal form; through the API you read the plan and build the milestones of POST /v1/enterprise/deals from it. A workspace holds up to 50 templates. Auth, roles, the workspace header and the shared limits are explained in Enterprise overview.
Endpoints
| Method | Path | Endpoint | Auth |
|---|---|---|---|
GET | /v1/enterprise/templates | List templates | viewer |
POST | /v1/enterprise/templates | Create a template | operator |
PATCH | /v1/enterprise/templates/:id | Update a template | operator |
DELETE | /v1/enterprise/templates/:id | Delete a template | operator |
Template milestones
Each plan has 1 to 20 milestones:
| Field | Type | Description |
|---|---|---|
title | string | 2 to 180 characters |
amount_pct | integer | 1 to 100. Share of the deal total |
amount | integer | Whole rupiah, 10,000 to 50,000,000,000 |
required_evidence | string | Optional, at most 300 characters |
due_offset_days | integer | Optional, 0 to 365. Days after the deal is created |
Either every milestone has amount_pct and they add up to exactly 100, or every milestone has amount. To turn percentages into amounts, round each share down to whole rupiah and add the remainder to the last milestone.
List templates
Every template in the workspace, sorted by name.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role: Any workspace member (
viewerand up). - Rate limit: 300 requests a minute per user, shared by every route.
- Idempotency: Not needed. Reading has no side effects.
Example request
curl https://api.rekberpay.com/v1/enterprise/templates \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY"const res = await fetch('https://api.rekberpay.com/v1/enterprise/templates', {
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
},
});
const data = await res.json();import os
import requests
resp = requests.get(
"https://api.rekberpay.com/v1/enterprise/templates",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/templates');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": [
{
"id": "66666664-6666-4666-8666-666666666666",
"name": "Software delivery, 3 milestones",
"description": "Fixed-price builds paid on design sign-off, beta and go-live.",
"milestones": [
{
"title": "Design sign-off",
"amount": 30000000,
"required_evidence": "Approved design document",
"due_offset_days": 14
},
{
"title": "Beta release",
"amount": 45000000,
"required_evidence": "Staging link and test report",
"due_offset_days": 45
}
],
"created_by": "88888883-8888-4888-8888-888888888888",
"created_at": "2026-10-01T22:33:06.162Z",
"updated_at": "2026-10-01T22:33:06.162Z"
},
{
"id": "77777774-7777-4777-8777-777777777777",
"name": "Standard procurement, 4 milestones",
"description": "Goods orders: deposit, production, shipment and acceptance.",
"milestones": [
{
"title": "Deposit",
"amount_pct": 20
},
{
"title": "Production complete",
"amount_pct": 30,
"required_evidence": "Factory photos and QC report",
"due_offset_days": 21
}
],
"created_by": "88888884-8888-4888-8888-888888888888",
"created_at": "2026-10-01T02:33:06.162Z",
"updated_at": "2026-10-01T02:33:06.162Z"
}
]
}Lists are cut to the first 2 items and long text is shortened here.
Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Create a template
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
operator,finance,adminorowner. - Rate limit: 60 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Body (JSON)
| Name | Type | Required | Description |
|---|---|---|---|
name | string | Yes | 2 to 120 characters |
milestones | array | Yes | 1 to 20 milestones, see above |
description | string | No | At most 500 characters |
Example request
curl -X POST https://api.rekberpay.com/v1/enterprise/templates \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: 99999994-9999-4999-8999-999999999999" \
-H "Content-Type: application/json" \
-d '{
"name": "Supplier order, 30/70",
"description": "Deposit on order, balance on delivery.",
"milestones": [
{
"title": "Deposit on purchase order",
"amount_pct": 30,
"required_evidence": "Signed purchase order"
},
{
"title": "Balance on delivery",
"amount_pct": 70,
"required_evidence": "Signed delivery note",
"due_offset_days": 30
}
]
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/templates', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '99999994-9999-4999-8999-999999999999',
},
body: JSON.stringify({
name: "Supplier order, 30/70",
description: "Deposit on order, balance on delivery.",
milestones: [
{
title: "Deposit on purchase order",
amount_pct: 30,
required_evidence: "Signed purchase order"
},
{
title: "Balance on delivery",
amount_pct: 70,
required_evidence: "Signed delivery note",
due_offset_days: 30
}
]
}),
});
const data = await res.json();import os
import requests
resp = requests.post(
"https://api.rekberpay.com/v1/enterprise/templates",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "99999994-9999-4999-8999-999999999999",
},
json={
"name": "Supplier order, 30/70",
"description": "Deposit on order, balance on delivery.",
"milestones": [
{
"title": "Deposit on purchase order",
"amount_pct": 30,
"required_evidence": "Signed purchase order",
},
{
"title": "Balance on delivery",
"amount_pct": 70,
"required_evidence": "Signed delivery note",
"due_offset_days": 30,
},
],
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/templates');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: 99999994-9999-4999-8999-999999999999',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Supplier order, 30/70',
'description' => 'Deposit on order, balance on delivery.',
'milestones' => [
[
'title' => 'Deposit on purchase order',
'amount_pct' => 30,
'required_evidence' => 'Signed purchase order',
],
[
'title' => 'Balance on delivery',
'amount_pct' => 70,
'required_evidence' => 'Signed delivery note',
'due_offset_days' => 30,
],
],
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
201 Created
{
"success": true,
"data": {
"id": "aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"name": "Supplier order, 30/70",
"description": "Deposit on order, balance on delivery.",
"milestones": [
{
"title": "Deposit on purchase order",
"amount_pct": 30,
"required_evidence": "Signed purchase order"
},
{
"title": "Balance on delivery",
"amount_pct": 70,
"required_evidence": "Signed delivery note",
"due_offset_days": 30
}
],
"created_by": "33333333-3333-4333-8333-333333333333"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field failed validation, for example percentages that do not add up to 100 |
409 | TEMPLATE_LIMIT | The workspace already has 50 templates |
409 | TEMPLATE_BUSY | Another template is being saved. Try again |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below operator |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
A real INVALID_BODY answer:
400 Bad Request
{
"success": false,
"error": {
"code": "INVALID_BODY",
"message": "Invalid request.",
"details": {
"formErrors": [],
"fieldErrors": {
"milestones": [
"amount_pct must add up to 100 (got 60)."
]
}
}
}
}Update a template
Any of name, description (null clears it) and milestones. A new milestones list replaces the whole plan.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
operator,finance,adminorowner. - Rate limit: 60 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The template ID |
Example request
curl -X PATCH https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: bbbbbbb4-bbbb-4bbb-8bbb-bbbbbbbbbbbb" \
-H "Content-Type: application/json" \
-d '{
"name": "Supplier order, deposit 30%"
}'const res = await fetch('https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'bbbbbbb4-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
},
body: JSON.stringify({
name: "Supplier order, deposit 30%"
}),
});
const data = await res.json();import os
import requests
resp = requests.patch(
"https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "bbbbbbb4-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
},
json={
"name": "Supplier order, deposit 30%",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Content-Type: application/json',
'Idempotency-Key: bbbbbbb4-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'Supplier order, deposit 30%',
]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"id": "aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"name": "Supplier order, deposit 30%",
"description": "Deposit on order, balance on delivery.",
"milestones": [
{
"title": "Deposit on purchase order",
"amount_pct": 30,
"required_evidence": "Signed purchase order"
},
{
"title": "Balance on delivery",
"amount_pct": 70,
"required_evidence": "Signed delivery note",
"due_offset_days": 30
}
],
"created_by": "33333333-3333-4333-8333-333333333333",
"created_at": "2026-10-13T14:33:16.338Z",
"updated_at": "2026-10-13T14:33:16.338Z"
}
}Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | A field failed validation, or nothing to update |
404 | NOT_FOUND | No such template in the workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below operator |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |
Delete a template
Deals already created from it do not change.
- Auth: A workspace API key (
Authorization: Bearer rpe_...), a personal API key or a dashboard session. - Role:
operator,finance,adminorowner. - Rate limit: 60 a minute per user for this route, plus the shared 300 a minute.
- Idempotency: Send an
Idempotency-Keyheader (UUID v4). A retry with the same key and body returns the first answer withIdempotent-Replay: true.
Path parameters
| Name | Type | Description |
|---|---|---|
id | UUID | The template ID |
Example request
curl -X DELETE https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa \
-H "Authorization: Bearer $REKBERPAY_WORKSPACE_KEY" \
-H "Idempotency-Key: ccccccc4-cccc-4ccc-8ccc-cccccccccccc"const res = await fetch('https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.REKBERPAY_WORKSPACE_KEY}`,
'Idempotency-Key': 'ccccccc4-cccc-4ccc-8ccc-cccccccccccc',
},
});
const data = await res.json();import os
import requests
resp = requests.delete(
"https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
headers={
"Authorization": f"Bearer {os.environ['REKBERPAY_WORKSPACE_KEY']}",
"Idempotency-Key": "ccccccc4-cccc-4ccc-8ccc-cccccccccccc",
},
timeout=30,
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://api.rekberpay.com/v1/enterprise/templates/aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('REKBERPAY_WORKSPACE_KEY'),
'Idempotency-Key: ccccccc4-cccc-4ccc-8ccc-cccccccccccc',
],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);Example response
200 OK
{
"success": true,
"data": {
"id": "aaaaaaa4-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"deleted": true
}
}Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | No such template in the workspace |
401 | UNAUTHORIZED, SESSION_EXPIRED, INVALID_TOKEN | No session or key, the session expired, or the key is wrong or revoked |
400 | INVALID_ACCOUNT | X-Enterprise-Account is not a UUID |
403 | NO_WORKSPACE | You are not an active member of that workspace, or the key belongs to another one |
403 | FORBIDDEN | Your role is below operator |
403 | SCOPE_REQUIRED | The workspace key only has the read scope |
403 | CSRF_FAILED | Session call without the matching X-CSRF-Token header |
429 | RATE_LIMITED | Over the rate limit. Wait for Retry-After seconds |