robovue-sdk 0.2.0__tar.gz

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.
@@ -0,0 +1,52 @@
1
+ # .NET
2
+ bin/
3
+ obj/
4
+ *.user
5
+ *.suo
6
+
7
+ # Terraform
8
+ **/.terraform/*
9
+ *.tfstate
10
+ *.tfstate.*
11
+ *.tfvars
12
+ crash.log
13
+ crash.*.log
14
+
15
+ # Python (sdks/python)
16
+ __pycache__/
17
+ *.egg-info/
18
+ .venv/
19
+ dist/
20
+ build/
21
+ .pytest_cache/
22
+
23
+ # OS / editor
24
+ .DS_Store
25
+ Thumbs.db
26
+ .vs/
27
+ .vscode/
28
+ *.swp
29
+
30
+ # Playwright MCP QA sessions (ephemeral verification evidence, see CLAUDE.md)
31
+ .playwright-mcp/
32
+ /*.png
33
+
34
+ # Personal local dev convenience scripts (not project code)
35
+ /dev-start.bat
36
+ /dev-start-app.bat
37
+ /dev-refresh-chrome.ps1
38
+
39
+ # impeccable (UI v2): per-machine state, captures and scratch
40
+ .impeccable/config.local.json
41
+ .impeccable/hook.*
42
+ .impeccable/live/sessions/
43
+ .impeccable/live/roots.json
44
+ .impeccable/live/inject-journal.json
45
+ .impeccable/review/
46
+ .impeccable/build/
47
+ design/scratch/
48
+ .impeccable/mocks/
49
+ .impeccable/questions/
50
+ design/brand/out/
51
+ tools/a11y/node_modules/
52
+ tools/a11y/a11y-report.json
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 — 2026-10-18
4
+
5
+ The first public release.
6
+
7
+ - `report_run` reports a run, signed with Robovue's v1 scheme: the signature covers a timestamp,
8
+ the endpoint and the delivery id as well as the body, so a captured request can't be replayed
9
+ and stops working after five minutes. Every retry is signed afresh.
10
+ - `try_report_run` reports a run without ever raising, so monitoring can't break the bot. Why a
11
+ report failed is in `RunReportOutcome.error`.
12
+ - `RunStatus.RUNNING` and a reused delivery id report a run that's still going, then its outcome.
13
+ `RunReportResult.delivery_id` gives back the id that was sent.
14
+ - `upload_attachment` attaches a screenshot or log to a failed run.
15
+ - `dry_run=True` checks every request in full and stores nothing; the result's `dry_run` and
16
+ `would_be` say what would have happened.
17
+ - `RobovueApiException` carries the server's `code`, `field` and `request_id`.
18
+ - Network errors and 5xx responses are retried with backoff, and a `429` after its `Retry-After`
19
+ (a wait over one minute is raised instead of blocking the bot).
20
+ - Reports are checked against the server's own rules before sending: names, times, categories, the
21
+ payload (including NaN and Infinity), the 64 KB limit and the delivery id. A `datetime` with no
22
+ time zone is UTC.
23
+ - A custom `transport` can return response headers as a third element, so a `429`'s `Retry-After`
24
+ is honoured.
25
+ - Standard library only, with type hints (`py.typed`).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robovue
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,429 @@
1
+ Metadata-Version: 2.5
2
+ Name: robovue-sdk
3
+ Version: 0.2.0
4
+ Summary: Report RPA and scripted bot runs to Robovue: signed requests, retries, checks before sending and dry runs.
5
+ Project-URL: Homepage, https://robovue.co.uk
6
+ Project-URL: Documentation, https://robovue.co.uk/api
7
+ Author: Robovue
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: automation,bots,monitoring,robovue,rpa,webhook
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: System :: Monitoring
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # robovue-sdk
29
+
30
+ Report your bots' runs to [Robovue](https://robovue.co.uk), which watches every bot in one place and
31
+ tells you when one fails or doesn't run. This package signs each report, retries when the network
32
+ or Robovue has a problem, checks a report against Robovue's rules before sending it, and can send
33
+ dry runs while you set it up. It uses only Python's standard library.
34
+
35
+ ## Install
36
+
37
+ ```shell
38
+ pip install robovue-sdk
39
+ ```
40
+
41
+ ## Requirements
42
+
43
+ - Python 3.9 or later, on any operating system. No other packages.
44
+ - A Robovue workspace. Signing up is free.
45
+
46
+ Can't install a package where your bot runs, such as Power Automate Desktop's built-in Python?
47
+ Connections → HTTP API in Robovue gives you the same request as plain Python, C# and shell code to
48
+ paste in, and the [wire contract](#the-wire-contract-any-language) below describes it for any
49
+ language.
50
+
51
+ ## Quickstart
52
+
53
+ 1. **Sign up** at [robovue.co.uk](https://robovue.co.uk).
54
+ 2. **Copy your credentials** from **Settings → Connections → HTTP API**: the webhook secret, and
55
+ your tenant ID, which is the long id in the address shown there
56
+ (`https://app.robovue.co.uk/ingest/<tenant ID>/runs`).
57
+ 3. **Keep them out of your code.** Set them as environment variables on the machine that runs the
58
+ bot, `ROBOVUE_TENANT_ID` and `ROBOVUE_WEBHOOK_SECRET`, or in your platform's credential store,
59
+ and create one client:
60
+
61
+ <!-- sample:begin imports create-client -->
62
+ ```python
63
+ import os
64
+ import uuid
65
+ from datetime import datetime, timezone
66
+
67
+ from robovue_sdk import FailureCategory, RobovueApiException, RobovueClient, RunReport, RunStatus
68
+
69
+ # One client for the whole process: it's safe to share between threads.
70
+ robovue = RobovueClient(
71
+ base_url="https://app.robovue.co.uk",
72
+ tenant_id=os.environ["ROBOVUE_TENANT_ID"],
73
+ webhook_secret=os.environ["ROBOVUE_WEBHOOK_SECRET"],
74
+ )
75
+ ```
76
+ <!-- sample:end -->
77
+
78
+ 4. **Wrap your bot's job.** Report the run as `RUNNING` when it starts, then as `SUCCESS` or
79
+ `FAILURE` when it ends, with the same delivery id so Robovue knows it's one run:
80
+
81
+ <!-- sample:begin quickstart -->
82
+ ```python
83
+ delivery_id = str(uuid.uuid4()) # one per run: it ties the run's reports together
84
+ started_at = datetime.now(timezone.utc)
85
+
86
+ robovue.try_report_run(
87
+ RunReport(
88
+ bot_id="invoice-bot-01",
89
+ platform="python",
90
+ status=RunStatus.RUNNING,
91
+ started_at=started_at,
92
+ ),
93
+ delivery_id=delivery_id,
94
+ )
95
+
96
+ try:
97
+ process_invoices() # your bot's work
98
+ except Exception as exc:
99
+ robovue.try_report_run(
100
+ RunReport(
101
+ bot_id="invoice-bot-01",
102
+ platform="python",
103
+ status=RunStatus.FAILURE,
104
+ started_at=started_at,
105
+ completed_at=datetime.now(timezone.utc),
106
+ failure_category=FailureCategory.APPLICATION_EXCEPTION,
107
+ payload={"error": str(exc)},
108
+ ),
109
+ delivery_id=delivery_id,
110
+ )
111
+ raise
112
+
113
+ robovue.try_report_run(
114
+ RunReport(
115
+ bot_id="invoice-bot-01",
116
+ platform="python",
117
+ status=RunStatus.SUCCESS,
118
+ started_at=started_at,
119
+ completed_at=datetime.now(timezone.utc),
120
+ ),
121
+ delivery_id=delivery_id,
122
+ )
123
+ ```
124
+ <!-- sample:end -->
125
+
126
+ `try_report_run` never raises, so a problem reaching Robovue can't break your bot. The job's own
127
+ exception is reported, then raised again, so the bot still fails the way it always did.
128
+
129
+ 5. **Check it arrives.** Run the bot once with [`dry_run`](#checking-a-new-integration-with-a-dry-run)
130
+ on, then for real, and watch the report land in the Verify step on Connections → HTTP API, in the
131
+ request log, and on Fleet.
132
+
133
+ ## Make missed runs alert
134
+
135
+ A failure alerts as soon as it's reported. A run that never happens can only be noticed if Robovue
136
+ knows when to expect it, so in **Manage bots**, register the bot (its id is `bot_id`) and give it a
137
+ schedule. Until a bot is registered, its runs appear under **Unregistered ids**, and each new id
138
+ counts toward your plan's bot limit.
139
+
140
+ ## Reporting in more detail
141
+
142
+ ### Failure categories
143
+
144
+ On a `FAILURE`, set `failure_category` to say why it failed. It's shown on the run, filterable on
145
+ Runs, and named in the alert. Leave it out if you don't know: Robovue records `Uncategorized`, and a
146
+ run can be reclassified later from its page.
147
+
148
+ <!-- sample:begin failure-category -->
149
+ ```python
150
+ result = robovue.report_run(
151
+ RunReport(
152
+ bot_id="invoice-bot-01",
153
+ platform="python",
154
+ status=RunStatus.FAILURE,
155
+ started_at=started_at,
156
+ completed_at=datetime.now(timezone.utc),
157
+ failure_category=FailureCategory.BUSINESS_EXCEPTION,
158
+ )
159
+ )
160
+ ```
161
+ <!-- sample:end -->
162
+
163
+ | Category | Use it for |
164
+ |---|---|
165
+ | `BUSINESS_EXCEPTION` | A business rule stopped the run: bad input data, a missing record, a rejected transaction. |
166
+ | `APPLICATION_EXCEPTION` | Something broke in the bot or the application it drives. |
167
+ | `TIMEOUT` | The run, or a step in it, took too long. |
168
+ | `AUTHENTICATION_ERROR` | The bot couldn't sign in to something it needs. |
169
+ | `CANCELLED` | A person or a scheduler stopped the run. |
170
+ | `UNCATEGORIZED` | You don't know. The default. |
171
+
172
+ ### Attaching context with `payload`
173
+
174
+ `payload` takes any JSON-serialisable value and is stored as sent, shown in full on the run's page.
175
+ Use it for whatever would help you debug the run later: an invoice number, the step that failed, a
176
+ stack trace. If it has an `error`, `errorMessage` or `message` key, the alert uses that text to say
177
+ what went wrong.
178
+
179
+ <!-- sample:begin payload -->
180
+ ```python
181
+ result = robovue.report_run(
182
+ RunReport(
183
+ bot_id="invoice-bot-01",
184
+ platform="python",
185
+ status=RunStatus.FAILURE,
186
+ started_at=started_at,
187
+ completed_at=datetime.now(timezone.utc),
188
+ failure_category=FailureCategory.TIMEOUT,
189
+ payload={
190
+ "invoiceId": "INV-2291",
191
+ "error": "Timed out waiting for the vendor portal",
192
+ },
193
+ )
194
+ )
195
+ ```
196
+ <!-- sample:end -->
197
+
198
+ The whole report, payload included, must be under 64 KB.
199
+
200
+ ### Attaching a screenshot or log to a failed run
201
+
202
+ On the Team plan and above, you can attach one screenshot or log excerpt to a `FAILURE` run once
203
+ it's reported. Screenshots are off for each bot until someone turns them on (**Manage bots →
204
+ Screenshots**), because a screenshot can capture credentials or other sensitive data on screen.
205
+
206
+ <!-- sample:begin attachment -->
207
+ ```python
208
+ robovue.upload_attachment(result.run_id, screenshot_bytes, "image/png")
209
+ ```
210
+ <!-- sample:end -->
211
+
212
+ `content_type` is `"image/png"`, `"image/jpeg"` or `"text/plain"`, up to 5 MB, one per run. Robovue
213
+ deletes attachments after 30 days, or 7 or 14 if an Admin chose that. An upload for a bot with
214
+ screenshots off, or on a plan without them, gets the same 404 as an unknown run, so catch it if a
215
+ missing screenshot shouldn't fail your bot.
216
+
217
+ ### Reporting without ever raising
218
+
219
+ Monitoring shouldn't become a new way for a bot to fail. `try_report_run` never raises for a failed
220
+ report: an invalid report, a rejection or a network problem comes back in the outcome for you to log.
221
+
222
+ <!-- sample:begin try-report -->
223
+ ```python
224
+ outcome = robovue.try_report_run(report)
225
+ if not outcome.succeeded:
226
+ print(f"Couldn't report the run to Robovue: {outcome.error}", file=sys.stderr)
227
+ ```
228
+ <!-- sample:end -->
229
+
230
+ ### When a report is rejected
231
+
232
+ `report_run` raises `RobovueApiException`. Branch on `code`, which is stable; the message is for
233
+ people. `field` names the field at fault when there is one, and `request_id` is worth quoting if you
234
+ contact support.
235
+
236
+ <!-- sample:begin rejected -->
237
+ ```python
238
+ try:
239
+ robovue.report_run(report)
240
+ except RobovueApiException as exc:
241
+ if exc.code != "timestamp_out_of_range":
242
+ raise
243
+ # This machine's clock is more than 5 minutes out. The message gives Robovue's time.
244
+ print(exc, file=sys.stderr)
245
+ ```
246
+ <!-- sample:end -->
247
+
248
+ Network errors and 5xx responses are retried automatically (3 attempts by default), and so is a
249
+ `429` after its `Retry-After`, unless that's more than a minute, when the 429 is raised instead of
250
+ blocking your bot. A report is checked against the server's own rules before anything is sent, so
251
+ most mistakes fail at once with a `ValueError`. A `datetime` with no time zone is treated as UTC.
252
+ Every code is in the [responses table](#responses) below, with whether a retry can help.
253
+
254
+ ### Checking a new integration with a dry run
255
+
256
+ With `dry_run=True`, every request is checked exactly as a real one would be (signature, fields,
257
+ plan limits, delivery id) but nothing is stored. The result says what would have happened, and a
258
+ request that would fail raises the real error.
259
+
260
+ <!-- sample:begin dry-run -->
261
+ ```python
262
+ robovue = RobovueClient(
263
+ base_url="https://app.robovue.co.uk",
264
+ tenant_id=tenant_id,
265
+ webhook_secret=webhook_secret,
266
+ dry_run=True, # checked in full, stored nowhere
267
+ )
268
+
269
+ result = robovue.report_run(report)
270
+ print(result.dry_run, result.would_be) # True created
271
+ ```
272
+ <!-- sample:end -->
273
+
274
+ Dry runs show in the request log, which warns you if any arrived in the last day, so a bot left in
275
+ dry-run mode doesn't go unnoticed. Turn it off before going live.
276
+
277
+ ### Runs that take a while
278
+
279
+ The quickstart reports `RUNNING` first. That's optional, but it means a bot that starts a job and
280
+ then hangs shows as Running straight away, rather than only when its next run fails to turn up.
281
+ Reusing the delivery id is the whole mechanism: a fresh id on the second report makes two runs,
282
+ one stuck Running and one that looks like nobody started it. A run left Running silences nothing:
283
+ if it never completes, the bot still goes Late and then Overdue on its schedule.
284
+
285
+ ## Using it in your bot
286
+
287
+ **One client per process.** `RobovueClient` holds no state between calls, so share one instance
288
+ across threads.
289
+
290
+ **Timeouts, proxies and TLS.** Each attempt times out after 30 seconds. Requests go through
291
+ Python's `urllib`, so they use the proxy in `HTTPS_PROXY` (or, on Windows, the system's proxy
292
+ settings) and the operating system's trusted certificates; set `SSL_CERT_FILE` to a certificate
293
+ bundle if your network inspects TLS with its own certificate. For anything else, pass
294
+ `transport=`: a function that takes the URL, a dict of headers and the body bytes, sends the POST,
295
+ and returns `(status, response_body, response_headers)`.
296
+
297
+ ## Options
298
+
299
+ | `RobovueClient(...)` | Default | What it does |
300
+ |---|---|---|
301
+ | `base_url` | required | `https://app.robovue.co.uk` |
302
+ | `tenant_id` | required | Your workspace's tenant ID. |
303
+ | `webhook_secret` | required | Signs every request. Never put it in source code. |
304
+ | `max_retry_attempts` | `3` | Attempts per call, the first included. |
305
+ | `retry_base_delay` | `0.2` | Seconds before the first retry; each later wait doubles. |
306
+ | `dry_run` | `False` | Check every request in full and store nothing. |
307
+ | `transport` | `urllib` | How requests are sent (see above). |
308
+
309
+ ## Troubleshooting
310
+
311
+ | What you see | What to do |
312
+ |---|---|
313
+ | `invalid_signature` | The secret is wrong or was rotated. Copy the current one from Connections → HTTP API. |
314
+ | `timestamp_out_of_range` | The machine's clock is more than 5 minutes out. Sync it (on Windows, `w32tm /resync`). |
315
+ | `unknown_tenant` | The tenant ID is wrong. Copy it from the address on Connections → HTTP API. |
316
+ | `bot_cap_reached` | A new bot id would go over your plan's bot limit. Delete a bot you no longer use, or change plan. |
317
+ | `payload_too_large` | The report is over 64 KB. Send less in `payload`, and attach a log file instead. |
318
+ | A 403 with no code | Something between your bot and Robovue blocked the request, such as a proxy rewriting it. Check with whoever runs your network, or send us the time it happened. |
319
+ | An `OSError` or `URLError` | The machine can't reach `app.robovue.co.uk`. Check its proxy and firewall. |
320
+ | `CERTIFICATE_VERIFY_FAILED` | Your network inspects TLS. Point `SSL_CERT_FILE` at a bundle that includes its certificate. |
321
+ | The report succeeds but nothing shows on Fleet | `dry_run` is still on, the bot is archived (see **Manage bots → Archived bots**), or the credentials belong to another workspace. |
322
+ | A bot stopped running and nobody was told | It has no schedule. See [Make missed runs alert](#make-missed-runs-alert). |
323
+
324
+ The request log, in Connections, shows every request from the last 7 days and the exact reason any
325
+ was turned away.
326
+
327
+ ## The wire contract (any language)
328
+
329
+ This is exactly what `RobovueClient` sends. From a language with no Robovue SDK, send the same
330
+ request yourself. The tables below are generated from the server's own contract, so they list every
331
+ field, header, response and limit, including the optional ones.
332
+
333
+ <!-- contract:begin — generated from IngestionContract by ContractDocsTests; don't edit by hand -->
334
+ ### `POST /ingest/{tenantId}/runs`
335
+
336
+ The endpoint both SDKs call, and the one to use from any other language. A JSON object with the fields below.
337
+
338
+ #### Headers
339
+
340
+ | Header | Value | Description |
341
+ |---|---|---|
342
+ | `Content-Type` | application/json | The body is JSON. A charset parameter is fine. |
343
+ | `X-Robovue-Delivery-Id` | 1–200 printable ASCII characters, no spaces | Unique to this run. Sending it again — a retry, or the Success/Failure that ends a Running run — updates or confirms the same run instead of creating a second one. A GUID, or your bot's own run id, both work. |
344
+ | `X-Robovue-Timestamp` | Unix time in whole seconds | When you signed the request. It must be within 5 minutes of Robovue's clock either way, so a captured request stops working within minutes. Sign each attempt, retries included, just before sending it. |
345
+ | `X-Robovue-Signature` | v1=&lt;lowercase hex&gt; | HMAC-SHA256, keyed with your webhook secret, of the timestamp, the word run, the delivery id — each followed by a line break — then the exact request body; hex-encoded in lowercase and prefixed with v1=. Sign the exact bytes you send: re-serialising the JSON after signing changes them. Several comma-separated v1= values are accepted, and any one matching is enough. |
346
+ | `X-Robovue-Dry-Run` | true | Checks the request exactly as a real one — authentication, every field, the delivery id, your plan's bot limit — and stores nothing. The response says what would have happened. Use it to test an integration without creating runs. Any other value, or no header, is an ordinary request. |
347
+
348
+ #### Body fields
349
+
350
+ | Field | Type | Required | Description |
351
+ |---|---|---|---|
352
+ | `botId` | string, ≤ 200 chars | Yes | Your name for the bot. Every run with the same botId is the same bot on your dashboard. Spaces and punctuation are fine; not / or \, no spaces at either end, and no tabs or line breaks. The same id registers and manages the bot through the Management API. |
353
+ | `platform` | string, ≤ 50 chars | Yes | What runs the bot, shown beside it on the dashboard — any short label, such as dotnet, python, go or blue-prism. Same whitespace rules as botId. |
354
+ | `status` | one of `Success`, `Failure`, `Running` | Yes | How the run went. Running reports a run that has started and not finished yet; send the same delivery id again with Success or Failure when it ends. Values are case-sensitive. |
355
+ | `startedAt` | timestamp | Yes | When the run started, as an RFC 3339 timestamp with a time zone offset — Z for UTC or ±hh:mm, e.g. 2026-07-17T09:00:00Z. Not before 2000, and not more than an hour ahead of Robovue's clock. Unix times aren't accepted. |
356
+ | `completedAt` | timestamp | Required when status is Success or Failure; leave it out when status is Running | When the run finished. Same format and limits as startedAt, and not before it. |
357
+ | `failureCategory` | one of `Uncategorized`, `BusinessException`, `ApplicationException`, `Timeout`, `AuthenticationError`, `Cancelled` | No — only with status Failure | Why the run failed, if your bot can tell. Leave it out if not — it becomes Uncategorized and can be changed later on the run's page. Values are case-sensitive. |
358
+ | `payload` | object | No | Anything that will help you debug the run later — an invoice number, the step that failed, a stack trace. Must be a JSON object; it's stored as sent and shown on the run's page. A top-level error, errorMessage or message string becomes the summary in the failure alert. Counts toward the 64 KB request limit. |
359
+
360
+ #### Responses
361
+
362
+ Every error body is `{"error": "…", "code": "…", "field": "…", "requestId": "…"}` — `field` only when the error is about one field — and every response carries an `X-Request-Id` header.
363
+
364
+ | Status | Code | Meaning | Retry? |
365
+ |---|---|---|---|
366
+ | 201 | — | A new run was recorded. Body: {"runId": "…", "duplicate": false} | No need — nothing more to send |
367
+ | 200 | — | The run was already known. duplicate is false when this completed a run reported as Running, and true when it repeated a report already recorded — a retry, which is safe. | No need — nothing more to send |
368
+ | 200 | — | A dry run passed every check. Body: {"dryRun": true, "wouldBe": "created"} — or "completed" or "duplicate". Nothing was stored. | No need — nothing more to send |
369
+ | 400 | `invalid_body` | The body isn't a single valid JSON object. The message gives the line and position. | No — fix the request first |
370
+ | 400 | `duplicate_field` | A field appears twice. | No — fix the request first |
371
+ | 400 | `unknown_field` | A field isn't one of the documented names. Names are case-sensitive; put your own data inside payload. | No — fix the request first |
372
+ | 400 | `missing_field` | A required field is missing, blank or null. field names it. | No — fix the request first |
373
+ | 400 | `invalid_field` | A field's value breaks its rule. field names it and the message says how. | No — fix the request first |
374
+ | 400 | `missing_delivery_id` | X-Robovue-Delivery-Id is missing. | No — fix the request first |
375
+ | 400 | `invalid_delivery_id` | X-Robovue-Delivery-Id is longer than 200 characters, or has spaces or non-ASCII characters. | No — fix the request first |
376
+ | 400 | `bot_cap_reached` | This would be a new bot and your plan's bot limit is reached. Archive and delete a bot you no longer use, or raise the limit on the Billing page. | No — fix the request first |
377
+ | 401 | `missing_signature` | X-Robovue-Signature is missing. | No — fix the request first |
378
+ | 401 | `missing_timestamp` | X-Robovue-Timestamp is missing. | No — fix the request first |
379
+ | 401 | `invalid_timestamp` | X-Robovue-Timestamp isn't whole Unix seconds — milliseconds are the usual cause. | No — fix the request first |
380
+ | 401 | `timestamp_out_of_range` | X-Robovue-Timestamp is more than 5 minutes from Robovue's clock. The message gives the server's time; fix the sending machine's clock. | Yes, once re-signed with the current time |
381
+ | 401 | `invalid_signature` | The signature doesn't match. Check the secret, and that you sign the timestamp, the delivery or run id and the exact bytes you send. | No — fix the request first |
382
+ | 404 | `unknown_tenant` | No workspace has this tenant id. Copy the URL from your Connect page. | No — fix the request first |
383
+ | 409 | `delivery_id_conflict` | The delivery id already belongs to a different bot's run. Reuse a delivery id only for the same bot's run. | No — fix the request first |
384
+ | 413 | `payload_too_large` | The body is over 64 KB. | No — fix the request first |
385
+ | 415 | `unsupported_media_type` | Content-Type isn't application/json. | No — fix the request first |
386
+ | 429 | `rate_limited` | Too many requests. Retry-After says how many seconds to wait. | Yes, after the Retry-After seconds |
387
+ | 500 | `internal_error` | Something went wrong on Robovue's side. Quote the requestId if it keeps happening. | Yes, with backoff — the same delivery id makes it safe |
388
+
389
+ #### Limits
390
+
391
+ | Limit | Value | Notes |
392
+ |---|---|---|
393
+ | Run report size | 64 KB | Per request, payload included. Anything larger is rejected with 413 — it isn't truncated. |
394
+ | Run reports | 600 per minute per workspace | Shared by the run report, Power Automate and UiPath endpoints. A start and its completion count as two. |
395
+ | Attachment size | 5 MB | One file per failed run: PNG, JPEG or UTF-8 text. |
396
+ | Attachment uploads | 50 per hour per workspace | Across all bots. |
397
+ | Attachment storage | 500 MB per workspace | Attachments are deleted after the workspace's retention, 30 days at most, which frees their space. |
398
+ | Management API | 300 requests per minute per workspace | Every /tenants/… request. |
399
+ | Per address | 3,000 requests per minute per client IP | Across ingestion and the Management API together, for every workspace sending from that address. |
400
+ <!-- contract:end -->
401
+
402
+ **Signing, in one line:** `"v1=" + hex(HMAC-SHA256(key: webhook secret, message: timestamp + "\nrun\n" + delivery id + "\n" + the exact body bytes))`,
403
+ lowercase, sent with the same timestamp in `X-Robovue-Timestamp` (Unix seconds). Sign each attempt, retries
404
+ included, just before sending it. Sign the bytes you actually send: re-serialising the JSON after signing (a
405
+ different key order, different whitespace) changes them and the request is rejected with `invalid_signature`.
406
+ Connections → HTTP API has a signature checker to compare yours against.
407
+
408
+ The attachment upload (`POST /ingest/{tenantId}/runs/{runId}/attachment`) uses the same scheme with the word
409
+ `attachment` in place of `run` and the run id (lowercase, hyphenated) in place of the delivery id, computed over
410
+ the raw file bytes instead of a JSON body. It has no delivery-id header: one attachment per run is what makes it
411
+ safe to retry.
412
+
413
+ This scheme is only for the `/ingest/{tenantId}/runs` endpoint. UiPath Orchestrator, Power Automate and
414
+ Automation Anywhere connect to Robovue without code; Connections in Robovue sets each one up.
415
+
416
+ ## Versions and support
417
+
418
+ - The package follows [semantic versioning](https://semver.org). `CHANGELOG.md`, in the source
419
+ distribution, lists what changed in each version.
420
+ - For bots written in C# or another .NET language, [`Robovue.Sdk`](https://www.nuget.org/packages/Robovue.Sdk)
421
+ is the same client.
422
+ - Questions and problems: [contact@robovue.co.uk](mailto:contact@robovue.co.uk). Quote the `RequestId` from
423
+ the error if there is one.
424
+ - Found a security problem? See [robovue.co.uk/security](https://robovue.co.uk/security).
425
+
426
+ ## Licence
427
+
428
+ MIT, so you can use, change and ship this package freely (see `LICENSE` in the package). Using the Robovue
429
+ service is covered by the [terms](https://robovue.co.uk/terms).