@deepdockai/cli 1.0.26 → 1.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/deepdock.js +2 -1
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
- package/skill/references/calling-actions.md +10 -9
- package/skill/references/clients/__pycache__/deepdock.cpython-312.pyc +0 -0
- package/skill/references/clients/__pycache__/deepdock.cpython-313.pyc +0 -0
- package/skill/references/clients/deepdock.py +10 -4
- package/skill/references/clients/deepdock.ts +10 -4
- package/skill/references/concepts.md +2 -1
- package/skill/references/discovery.md +4 -4
- package/skill/references/errors.md +1 -1
- package/skill/references/example-integration.md +9 -9
- package/skill/references/idempotency.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.27
|
|
4
|
+
|
|
5
|
+
- An update is called with `PATCH` again; a read and a create stay `POST`. All three take a flat JSON body and nothing in the URL. The method must be the action's own: any other answers `405 invalid_request` with `Allow` naming the one method (`POST` or `PATCH`), so a `POST` to an update is refused and does nothing. A query string on an action path is still `400 invalid_request`. A write still needs an `Idempotency-Key`; a read ignores it.
|
|
6
|
+
- Each entry of the action index has a `method` (`POST` or `PATCH`) beside its `operation` (`read`, `create` or `update`).
|
|
7
|
+
- The bundled clients' `write` takes the action's operation after the action name (`write(deviceId, system, action, 'create' | 'update', key, input)`) and sends `POST` for a create and `PATCH` for an update; `PendingWrite` carries it as `operation`. Reads are unchanged. Idempotency handling is unchanged.
|
|
8
|
+
- The skill's references describe the contract.
|
|
9
|
+
|
|
3
10
|
## 1.0.26
|
|
4
11
|
|
|
5
12
|
- Every action is called with `POST /v1/devices/{deviceId}/systems/{system}/actions/{action}` and its inputs in a flat JSON body, reads and updates included. There is no `GET` read and no `PATCH` update any more: any other method answers `405 invalid_request` with `Allow: POST`, and a query string on an action path answers `400 invalid_request` ("inputs go in the JSON body, not in the URL"), so no value reaches a URL, a request log or a proxy. A read takes `limit` (a JSON integer) and `cursor` (a string) in the same body; a write still needs an `Idempotency-Key`, a read ignores it. Values are typed as given: an integer input is a JSON number, and `"1001"` is refused where it is declared as one.
|
package/dist/deepdock.js
CHANGED
|
@@ -11241,6 +11241,7 @@ var actionRow = (action) => {
|
|
|
11241
11241
|
const {
|
|
11242
11242
|
name,
|
|
11243
11243
|
operation: _operation,
|
|
11244
|
+
method: _method,
|
|
11244
11245
|
input: _input,
|
|
11245
11246
|
output: _output,
|
|
11246
11247
|
description,
|
|
@@ -11439,7 +11440,7 @@ import { spawn } from "node:child_process";
|
|
|
11439
11440
|
import { homedir, hostname } from "node:os";
|
|
11440
11441
|
|
|
11441
11442
|
// src/version.ts
|
|
11442
|
-
var VERSION = "1.0.
|
|
11443
|
+
var VERSION = "1.0.27";
|
|
11443
11444
|
|
|
11444
11445
|
// src/runtime.ts
|
|
11445
11446
|
var DEFAULT_BASE_URL = "https://api.deepdock.ai";
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -22,7 +22,8 @@ integration against a demo device. It is not for building or changing actions: D
|
|
|
22
22
|
screen, and from then on it belongs to your organisation until you release it.
|
|
23
23
|
3. A device reports the **systems** it runs; each system has **actions**, deterministic operations
|
|
24
24
|
that Deepdock builds and verifies before offering them.
|
|
25
|
-
4. Every action
|
|
25
|
+
4. Every action takes a flat JSON body, never a value in the URL: `POST` for a read or create,
|
|
26
|
+
`PATCH` for an update. A **read** (the
|
|
26
27
|
action's `operation`) also takes `limit` and `cursor` in that body to page; a **write** (`create`
|
|
27
28
|
or `update`) needs an `Idempotency-Key` as well.
|
|
28
29
|
5. Every write needs an `Idempotency-Key`: a new key per intended write, the same key on every retry,
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
All paths are under `/v1/devices/{deviceId}/systems/{system}/actions/{action}`. `deviceId` is the
|
|
4
4
|
UUID from pairing; `system` and `action` are the slugs from the action index.
|
|
5
5
|
|
|
6
|
-
## Calling an action: POST
|
|
6
|
+
## Calling an action: POST, or PATCH for an update
|
|
7
7
|
|
|
8
|
-
Every action
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
`405 invalid_request` with `Allow
|
|
8
|
+
Every action is called with a flat JSON object as the body. A read and a create are `POST`, an
|
|
9
|
+
update is `PATCH`. What the action does is its `operation` (`read`, `create` or `update`), and its
|
|
10
|
+
`method` (`POST` or `PATCH`), in the action index and the OpenAPI document. Any other method
|
|
11
|
+
answers `405 invalid_request` with `Allow` naming the action's one method, so a `POST` to an update
|
|
12
|
+
is refused, not run.
|
|
12
13
|
|
|
13
14
|
Inputs are typed as the action's schema declares them and are never sent as text when they are not:
|
|
14
15
|
an integer is a JSON number (`"patientId": 4711`, not `"4711"`; the text form is refused with
|
|
@@ -64,7 +65,7 @@ curl -X POST "$BASE/v1/devices/$DEVICE/systems/example-erp/actions/createNote" \
|
|
|
64
65
|
|
|
65
66
|
- A create answers `201` with `{ data: { <returned fields> } }`, typically the new record's id. A
|
|
66
67
|
replay of the same key answers `200` with the same data and `Idempotent-Replayed: true`.
|
|
67
|
-
- An update is a `
|
|
68
|
+
- An update is a `PATCH` to the same path, with the same body rules, and answers `200` whether it ran now or is a replay; the header tells
|
|
68
69
|
you which.
|
|
69
70
|
- The returned id is the system's own id (for example `noteId`). Store it as the system gave it;
|
|
70
71
|
do not predict or re-key it. Result field types come from each action's own output schema: do not
|
|
@@ -98,14 +99,14 @@ Never invent an id, copy one from another device, or hard-code one from a test r
|
|
|
98
99
|
from the action's own output schema.
|
|
99
100
|
- **Mark every test text `TEST-DEEPDOCK-<id>`** on the demo device only, and list what you created
|
|
100
101
|
([testing.md](testing.md)).
|
|
101
|
-
- A call with a query string, or with
|
|
102
|
+
- A call with a query string, or with a method other than the action's own, is refused (`400` and `405`), so a
|
|
102
103
|
client library that puts parameters in the URL for you needs its body mode.
|
|
103
104
|
|
|
104
105
|
## Refusals specific to the call shape
|
|
105
106
|
|
|
106
107
|
| Answer | Cause | Fix |
|
|
107
108
|
|---|---|---|
|
|
108
|
-
| `405 invalid_request`, `Allow: POST` |
|
|
109
|
+
| `405 invalid_request`, `Allow: POST` or `Allow: PATCH` | a method other than the action's own (for example `POST` to an update, or `PATCH` to a read or create) | use the method `Allow` names: `POST` for a read or create, `PATCH` for an update |
|
|
109
110
|
| `400 invalid_request` | a query string on an action path | move every value into the JSON body |
|
|
110
111
|
| `400 invalid_request` | an input of the wrong type, such as `"4711"` where an integer is declared | send the type the schema declares |
|
|
111
112
|
|
|
@@ -138,7 +139,7 @@ attempts per key per rolling 60 seconds, whose `429` carries neither the header
|
|
|
138
139
|
| Header | When | Meaning |
|
|
139
140
|
|---|---|---|
|
|
140
141
|
| `Idempotent-Replayed: true` | a write's `200` | the stored result of an earlier run; nothing written again |
|
|
141
|
-
| `Allow` | `405 invalid_request` |
|
|
142
|
+
| `Allow` | `405 invalid_request` | the action's one method: `POST` for a read or create, `PATCH` for an update |
|
|
142
143
|
| `WWW-Authenticate: Bearer` | `401` | the key was refused |
|
|
143
144
|
| `Retry-After` | `429` | seconds to wait, when present |
|
|
144
145
|
|
|
Binary file
|
|
Binary file
|
|
@@ -105,10 +105,15 @@ class Deepdock:
|
|
|
105
105
|
return
|
|
106
106
|
cursor = page.next_cursor
|
|
107
107
|
|
|
108
|
-
def write(self, device_id: str, system: str, action: str, idempotency_key: str,
|
|
108
|
+
def write(self, device_id: str, system: str, action: str, operation: str, idempotency_key: str,
|
|
109
109
|
body: Dict[str, InputValue]) -> WriteResult:
|
|
110
|
-
"""Runs a write action
|
|
111
|
-
|
|
110
|
+
"""Runs a write action: POST for operation "create", PATCH for "update" (from the action index; any
|
|
111
|
+
other method answers 405 with `Allow` naming the right one). `body` values are typed as the
|
|
112
|
+
action's schema declares. Any other `operation` raises ValueError before anything is sent."""
|
|
113
|
+
if operation not in ("create", "update"):
|
|
114
|
+
raise ValueError(f"operation must be 'create' or 'update', got {operation!r}")
|
|
115
|
+
data, headers = self._send("PATCH" if operation == "update" else "POST",
|
|
116
|
+
self._action_path(device_id, system, action), json=body,
|
|
112
117
|
headers={"Idempotency-Key": idempotency_key})
|
|
113
118
|
return WriteResult(data=data, replayed=headers.get("Idempotent-Replayed") == "true")
|
|
114
119
|
|
|
@@ -150,6 +155,7 @@ class PendingWrite:
|
|
|
150
155
|
device_id: str
|
|
151
156
|
system: str
|
|
152
157
|
action: str
|
|
158
|
+
operation: str # create | update, from the action index
|
|
153
159
|
input: Dict[str, InputValue]
|
|
154
160
|
status: str = "pending" # pending | sent | done | unknown | refused
|
|
155
161
|
conflict_retries: int = 0
|
|
@@ -161,7 +167,7 @@ class PendingWrite:
|
|
|
161
167
|
def attempt(dd: Deepdock, row: PendingWrite) -> PendingWrite:
|
|
162
168
|
"""One attempt; returns the row with its new status. The caller persists it."""
|
|
163
169
|
try:
|
|
164
|
-
res = dd.write(row.device_id, row.system, row.action, row.key, row.input)
|
|
170
|
+
res = dd.write(row.device_id, row.system, row.action, row.operation, row.key, row.input)
|
|
165
171
|
return replace(row, status="done", result=res.data)
|
|
166
172
|
except DeepdockTransportError:
|
|
167
173
|
return replace(row, status="sent")
|
|
@@ -57,6 +57,8 @@ export type Page<Item> = { items: Item[]; nextCursor: string | null }
|
|
|
57
57
|
export type WriteResult<T> = { data: T; replayed: boolean }
|
|
58
58
|
/** A flat JSON value as the action's schema declares it: a number is a number, never its text. */
|
|
59
59
|
export type InputValue = string | number | boolean
|
|
60
|
+
/** What a write action does, from `operation` in the action index: a create is POST, an update PATCH. */
|
|
61
|
+
export type WriteOperation = 'create' | 'update'
|
|
60
62
|
export type ActionInput = Record<string, InputValue>
|
|
61
63
|
|
|
62
64
|
export type SystemStatus = 'running' | 'stopped' | 'missing' | 'unknown'
|
|
@@ -229,18 +231,21 @@ export class Deepdock {
|
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
/**
|
|
232
|
-
* Runs a write action
|
|
233
|
-
*
|
|
234
|
+
* Runs a write action: POST for a `create`, PATCH for an `update`. Take `operation` from the
|
|
235
|
+
* action index; any other method answers 405 with `Allow` naming the right one. `input` values
|
|
236
|
+
* are typed as the action's schema declares. `idempotencyKey` is new per intended write and the
|
|
237
|
+
* same on every retry.
|
|
234
238
|
*/
|
|
235
239
|
async write<Result>(
|
|
236
240
|
deviceId: string,
|
|
237
241
|
system: string,
|
|
238
242
|
action: string,
|
|
243
|
+
operation: WriteOperation,
|
|
239
244
|
idempotencyKey: string,
|
|
240
245
|
input: ActionInput,
|
|
241
246
|
): Promise<WriteResult<Result>> {
|
|
242
247
|
const { data, headers } = await this.send<Result>(this.actionPath(deviceId, system, action), {
|
|
243
|
-
method: 'POST',
|
|
248
|
+
method: operation === 'update' ? 'PATCH' : 'POST',
|
|
244
249
|
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
|
|
245
250
|
body: JSON.stringify(input),
|
|
246
251
|
})
|
|
@@ -285,6 +290,7 @@ export type PendingWrite = {
|
|
|
285
290
|
deviceId: string
|
|
286
291
|
system: string
|
|
287
292
|
action: string
|
|
293
|
+
operation: WriteOperation
|
|
288
294
|
input: ActionInput
|
|
289
295
|
status: PendingStatus
|
|
290
296
|
conflictRetries: number
|
|
@@ -311,7 +317,7 @@ const RETRY_SAME_KEY = new Set([
|
|
|
311
317
|
/** One attempt; returns the row with its new status. The caller persists it. */
|
|
312
318
|
export const attempt = async (dd: Deepdock, row: PendingWrite): Promise<PendingWrite> => {
|
|
313
319
|
try {
|
|
314
|
-
const { data } = await dd.write(row.deviceId, row.system, row.action, row.key, row.input)
|
|
320
|
+
const { data } = await dd.write(row.deviceId, row.system, row.action, row.operation, row.key, row.input)
|
|
315
321
|
return { ...row, status: 'done', result: data }
|
|
316
322
|
} catch (err) {
|
|
317
323
|
if (err instanceof DeepdockTransportError) return { ...row, status: 'sent' }
|
|
@@ -72,7 +72,8 @@ offering it; it then runs the same way every time. Actions are
|
|
|
72
72
|
**assigned** to your organisation by Deepdock, so the set you can call is yours, not the whole
|
|
73
73
|
catalogue.
|
|
74
74
|
|
|
75
|
-
- Every action
|
|
75
|
+
- Every action takes a flat JSON body; its `operation` says what it does: `POST` for a read or a
|
|
76
|
+
create, `PATCH` for an update.
|
|
76
77
|
- A **read** also pages with `limit` and `cursor` in the body.
|
|
77
78
|
- A **write** is a `create` (answers `201`) or an `update` (answers `200`), and needs an
|
|
78
79
|
`Idempotency-Key` header.
|
|
@@ -25,9 +25,9 @@ document into the repository and refresh it with the same command when you touch
|
|
|
25
25
|
What to read off it:
|
|
26
26
|
|
|
27
27
|
- **`paths`**: one operation per action at
|
|
28
|
-
`/v1/devices/{deviceId}/systems/{system}/actions/{action}`;
|
|
29
|
-
its `operation` says
|
|
30
|
-
|
|
28
|
+
`/v1/devices/{deviceId}/systems/{system}/actions/{action}`; each is a `post` (read, create) or a `patch` (update);
|
|
29
|
+
its `operation` says which. Any other method answers `405 invalid_request` with `Allow` naming
|
|
30
|
+
the right one.
|
|
31
31
|
- **`summary` and `description`**: what the action does and what it changes in the system, what
|
|
32
32
|
an omitted optional input becomes, action-specific refusals (`code` and when), and what the
|
|
33
33
|
system only warns about and what the action does then. Read the description before mapping your
|
|
@@ -56,7 +56,7 @@ curl "$BASE/v1/devices/$DEVICE/systems/example-erp/actions" \
|
|
|
56
56
|
-H "Authorization: Bearer $DEEPDOCK_API_KEY"
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Answers `{ data: [ { name, operation, description, input, output } ] }`, filtered to your assigned
|
|
59
|
+
Answers `{ data: [ { name, operation, method, description, input, output } ] }`, filtered to your assigned
|
|
60
60
|
actions, with the same JSON Schemas the OpenAPI document carries. Use it at runtime to confirm an
|
|
61
61
|
action exists before offering it in your UI, or to validate input client-side before sending.
|
|
62
62
|
|
|
@@ -17,7 +17,7 @@ with the **same** `Idempotency-Key`); **Show** says what the end user should see
|
|
|
17
17
|
|
|
18
18
|
| Code | Status | Meaning | Retry | Show |
|
|
19
19
|
|---|---|---|---|---|
|
|
20
|
-
| `invalid_request` | 400 (405 for a method
|
|
20
|
+
| `invalid_request` | 400 (405 for a method other than the action's own) | malformed request: bad `limit` or `cursor` in a read's body; missing or malformed `Idempotency-Key`; a body that is not an object; an action input that is unknown, missing, too short or long, out of bounds or not an allowed value (`fields` names each); a malformed pairing code or device id; a query string on an action path (inputs go in the JSON body), an input of the wrong type, such as `"4711"` where an integer is declared, an action called with a method other than its own (`405`, `Allow` names the one: `POST` for a read or create, `PATCH` for an update) | no; fix the code | nothing; a developer bug |
|
|
21
21
|
| `payload_too_large` | 413 | the request body is over 1 MiB | no; send less | nothing; a developer bug, or shorten the text |
|
|
22
22
|
| `unauthenticated` | 401 | the key is missing, malformed, for the other environment, unknown, revoked or expired | no | "Deepdock API key refused; check it in the dashboard" to an operator |
|
|
23
23
|
| `partner_not_approved` | 403 | your organisation is waiting for approval by Deepdock; no key of it works yet. Keys made before the approval start working once it is given | no, until Deepdock approves; then yes | "our Deepdock account is waiting for approval; contact Deepdock" |
|
|
@@ -23,7 +23,7 @@ import { Deepdock, DeepdockError, DeepdockTransportError, attempt, type PendingW
|
|
|
23
23
|
const dd = new Deepdock(process.env.DEEPDOCK_API_KEY!) // base from DEEPDOCK_BASE_URL, else production; 30 s timeout
|
|
24
24
|
const page = await dd.read<Note>(deviceId, 'example-erp', 'searchNotes', { text: 'TEST-DEEPDOCK-' }, { limit: 50 })
|
|
25
25
|
for await (const p of dd.readAll<Note>(deviceId, 'example-erp', 'searchNotes', { text: 'TEST-DEEPDOCK-' })) { /* every page */ }
|
|
26
|
-
const { data, replayed } = await dd.write<{ noteId: number }>(deviceId, 'example-erp', 'createNote', key, input)
|
|
26
|
+
const { data, replayed } = await dd.write<{ noteId: number }>(deviceId, 'example-erp', 'createNote', 'create', key, input)
|
|
27
27
|
const systems = await dd.systems(deviceId) // live; can throw device_offline
|
|
28
28
|
const device = await dd.pair('K7Q-4MX', 'Demo') // keep device.id
|
|
29
29
|
```
|
|
@@ -33,7 +33,7 @@ from deepdock import Deepdock, DeepdockError, DeepdockTransportError, PendingWri
|
|
|
33
33
|
|
|
34
34
|
dd = Deepdock(os.environ["DEEPDOCK_API_KEY"])
|
|
35
35
|
page = dd.read(device_id, "example-erp", "searchNotes", {"text": "TEST-DEEPDOCK-"}, limit=50)
|
|
36
|
-
res = dd.write(device_id, "example-erp", "createNote", key, body) # res.data, res.replayed
|
|
36
|
+
res = dd.write(device_id, "example-erp", "createNote", "create", key, body) # res.data, res.replayed
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Errors: `DeepdockError` has `status`, `code`, `requestId`, `systems`; switch on `code`
|
|
@@ -42,7 +42,7 @@ Errors: `DeepdockError` has `status`, `code`, `requestId`, `systems`; switch on
|
|
|
42
42
|
|
|
43
43
|
## Sending a write: persist first, then one attempt at a time
|
|
44
44
|
|
|
45
|
-
Persist the pending write (key, device, action, input, status) **before** the first send,
|
|
45
|
+
Persist the pending write (key, device, action, operation, input, status) **before** the first send,
|
|
46
46
|
so a crash between send and answer cannot produce a second record under a new key. `attempt()`
|
|
47
47
|
moves the row to `done`, `sent` (retry later, same key), `unknown` (read back before anything
|
|
48
48
|
else) or `refused` (nothing was written; a changed input gets a new key). A job retries `sent`
|
|
@@ -51,7 +51,7 @@ rows with backoff: seconds, then minutes, for hours if the device stays offline.
|
|
|
51
51
|
```ts
|
|
52
52
|
const row: PendingWrite = {
|
|
53
53
|
key: `order-${order.id}-create-note`, // 8 to 128 chars of A-Z a-z 0-9 _ -, stable per business event
|
|
54
|
-
deviceId, system: 'example-erp', action: 'createNote',
|
|
54
|
+
deviceId, system: 'example-erp', action: 'createNote', operation: 'create',
|
|
55
55
|
input, status: 'pending', conflictRetries: 0,
|
|
56
56
|
}
|
|
57
57
|
await store.save(row) // before the first send
|
|
@@ -74,7 +74,7 @@ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
|
74
74
|
export const run = async (store: { save: (row: PendingWrite) => Promise<void> }) => {
|
|
75
75
|
const marker = `TEST-DEEPDOCK-${randomUUID().replaceAll('-', '').slice(0, 6).toUpperCase()}`
|
|
76
76
|
let row: PendingWrite = {
|
|
77
|
-
key: `demo-${marker}-create`, deviceId, system, action: 'createNote',
|
|
77
|
+
key: `demo-${marker}-create`, deviceId, system, action: 'createNote', operation: 'create',
|
|
78
78
|
input: { text: marker, orderNumber: '1001' },
|
|
79
79
|
status: 'pending', conflictRetries: 0,
|
|
80
80
|
}
|
|
@@ -132,8 +132,8 @@ curl -sS -i -X POST "$BASE/v1/devices/$DEVICE/systems/example-erp/actions/create
|
|
|
132
132
|
-H "$AUTH" -H "Idempotency-Key: demo-$(date +%s)-create" -H 'Content-Type: application/json' \
|
|
133
133
|
-d '{"text":"TEST-DEEPDOCK-K3M9QA","orderNumber":"1001"}'
|
|
134
134
|
|
|
135
|
-
# An update:
|
|
136
|
-
curl -sS -i -X
|
|
135
|
+
# An update: PATCH, same rules
|
|
136
|
+
curl -sS -i -X PATCH "$BASE/v1/devices/$DEVICE/systems/example-erp/actions/updateNote" \
|
|
137
137
|
-H "$AUTH" -H "Idempotency-Key: demo-$(date +%s)-update" -H 'Content-Type: application/json' \
|
|
138
138
|
-d '{"noteId":1001,"text":"TEST-DEEPDOCK-K3M9QA edited"}'
|
|
139
139
|
|
|
@@ -143,7 +143,7 @@ curl -sS -X DELETE "$BASE/v1/devices/$DEVICE" -H "$AUTH"
|
|
|
143
143
|
|
|
144
144
|
A port in another language keeps the clients' shape: one `send` that sets the bearer header,
|
|
145
145
|
parses the envelope, throws a typed error with `code` on `{ error }` and a transport error on no
|
|
146
|
-
answer or non-JSON; `read` as a POST with `limit` and `cursor` in the body; `write` with `Idempotency-Key` and the
|
|
146
|
+
answer or non-JSON; `read` as a POST with `limit` and `cursor` in the body; `write` (POST for a create, PATCH for an update) with `Idempotency-Key` and the
|
|
147
147
|
`Idempotent-Replayed` header; `attempt` over a persisted pending write.
|
|
148
148
|
|
|
149
149
|
## Mapping the user's intent to a call (checklist)
|
|
@@ -151,7 +151,7 @@ answer or non-JSON; `read` as a POST with `limit` and `cursor` in the body; `wri
|
|
|
151
151
|
1. Which device? From your product's configuration for that customer site; never from the
|
|
152
152
|
user's free text.
|
|
153
153
|
2. Is the action active? `systems()` once per session; show paused features as paused.
|
|
154
|
-
3. Which action and operation? From the stored index for that system.
|
|
154
|
+
3. Which action and operation (create is POST, update is PATCH)? From the stored index for that system.
|
|
155
155
|
4. Does the write need a reference (author user, open case, record id)? Read it first.
|
|
156
156
|
5. Build the input in the types the schema declares; validate against the schema (`pattern`, `minLength`, `maxLength`,
|
|
157
157
|
`enum`) before sending so the user sees the rule where they typed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Idempotency for writes
|
|
2
2
|
|
|
3
|
-
Every write (a `create` or `update
|
|
3
|
+
Every write (a `create` action, called with `POST`, or an `update`, called with `PATCH`) carries an `Idempotency-Key` header. Without it the request is
|
|
4
4
|
refused with `400 invalid_request`. A read ignores the header.
|
|
5
5
|
|
|
6
6
|
## The key
|