{
	"openapi": "3.0.0",
	"components": {
		"examples": {},
		"headers": {},
		"parameters": {},
		"requestBodies": {},
		"responses": {},
		"schemas": {
			"AccountecProfileConfig": {
				"description": "Settings for the Accountec delivery profile. Preliminary: not yet\nconfirmed with Accountec.",
				"properties": {
					"agencyId": {
						"type": "string",
						"description": "The agency's id in Accountec, when it is not already in the URL."
					},
					"headerName": {
						"type": "string",
						"description": "The header that carries the shared secret. Defaults to Authorization."
					},
					"serviceName": {
						"type": "string",
						"description": "The service name Accountec assigned to Senseworks."
					}
				},
				"type": "object",
				"additionalProperties": false
			},
			"WebhookSubscriptionCreated": {
				"description": "A new subscription, with its signing secret. The secret is only in the\ncreation response, and a same-key retry within 24 hours returns that\nresponse again. Subscription reads never return it; rotate it if it is lost.",
				"properties": {
					"id": {
						"type": "string"
					},
					"url": {
						"type": "string",
						"description": "Where deliveries are POSTed. https only."
					},
					"eventTypes": {
						"items": {
							"type": "string"
						},
						"type": "array",
						"description": "The event types delivered. Empty means every type."
					},
					"companyDurableKey": {
						"type": "string",
						"description": "Only events for this company. Absent means every company."
					},
					"profile": {
						"type": "string",
						"enum": [
							"senseworks",
							"accountec"
						],
						"description": "How deliveries are shaped. \"senseworks\" unless a portal needs its own."
					},
					"profileConfig": {
						"$ref": "#/components/schemas/AccountecProfileConfig"
					},
					"status": {
						"type": "string",
						"enum": [
							"active",
							"disabled",
							"failing"
						],
						"description": "\"failing\" means the last 20 deliveries failed. It still delivers."
					},
					"description": {
						"type": "string"
					},
					"createdAt": {
						"type": "string"
					},
					"updatedAt": {
						"type": "string"
					},
					"secretRotatedAt": {
						"type": "string",
						"description": "When the secret was last rotated. The previous one signs for 24 hours after."
					},
					"secret": {
						"type": "string"
					}
				},
				"required": [
					"id",
					"url",
					"eventTypes",
					"profile",
					"status",
					"createdAt",
					"updatedAt",
					"secret"
				],
				"type": "object",
				"additionalProperties": false
			},
			"Record_string.string_": {
				"properties": {},
				"additionalProperties": {
					"type": "string"
				},
				"type": "object",
				"description": "Construct a type with a set of properties K of type T"
			},
			"Problem": {
				"description": "An error in the /v2 surface, served as application/problem+json.",
				"properties": {
					"type": {
						"type": "string"
					},
					"title": {
						"type": "string"
					},
					"status": {
						"type": "number",
						"format": "double"
					},
					"errorCode": {
						"type": "string",
						"description": "Stable code to branch on."
					},
					"detail": {
						"type": "string"
					},
					"candidates": {
						"items": {
							"$ref": "#/components/schemas/Record_string.string_"
						},
						"type": "array",
						"description": "Present on 409 placement_ambiguous and assignment_ambiguous."
					}
				},
				"required": [
					"type",
					"title",
					"status",
					"errorCode"
				],
				"type": "object",
				"additionalProperties": false
			},
			"CreateWebhookSubscription": {
				"properties": {
					"url": {
						"type": "string"
					},
					"eventTypes": {
						"items": {
							"type": "string"
						},
						"type": "array",
						"description": "Event types to deliver. Leave out for every type."
					},
					"companyDurableKey": {
						"type": "string"
					},
					"profile": {
						"type": "string",
						"enum": [
							"senseworks",
							"accountec"
						]
					},
					"profileConfig": {
						"$ref": "#/components/schemas/AccountecProfileConfig"
					},
					"description": {
						"type": "string"
					}
				},
				"required": [
					"url"
				],
				"type": "object",
				"additionalProperties": false
			},
			"WebhookSubscription": {
				"description": "A webhook subscription: where events for this organisation are delivered.",
				"properties": {
					"id": {
						"type": "string"
					},
					"url": {
						"type": "string",
						"description": "Where deliveries are POSTed. https only."
					},
					"eventTypes": {
						"items": {
							"type": "string"
						},
						"type": "array",
						"description": "The event types delivered. Empty means every type."
					},
					"companyDurableKey": {
						"type": "string",
						"description": "Only events for this company. Absent means every company."
					},
					"profile": {
						"type": "string",
						"enum": [
							"senseworks",
							"accountec"
						],
						"description": "How deliveries are shaped. \"senseworks\" unless a portal needs its own."
					},
					"profileConfig": {
						"$ref": "#/components/schemas/AccountecProfileConfig"
					},
					"status": {
						"type": "string",
						"enum": [
							"active",
							"disabled",
							"failing"
						],
						"description": "\"failing\" means the last 20 deliveries failed. It still delivers."
					},
					"description": {
						"type": "string"
					},
					"createdAt": {
						"type": "string"
					},
					"updatedAt": {
						"type": "string"
					},
					"secretRotatedAt": {
						"type": "string",
						"description": "When the secret was last rotated. The previous one signs for 24 hours after."
					}
				},
				"required": [
					"id",
					"url",
					"eventTypes",
					"profile",
					"status",
					"createdAt",
					"updatedAt"
				],
				"type": "object",
				"additionalProperties": false
			},
			"WebhookSubscriptionPage": {
				"properties": {
					"items": {
						"items": {
							"$ref": "#/components/schemas/WebhookSubscription"
						},
						"type": "array"
					},
					"nextCursor": {
						"type": "string",
						"description": "Pass as `cursor` for the next page. Absent on the last page."
					}
				},
				"required": [
					"items"
				],
				"type": "object",
				"additionalProperties": false
			},
			"UpdateWebhookSubscription": {
				"properties": {
					"url": {
						"type": "string"
					},
					"eventTypes": {
						"items": {
							"type": "string"
						},
						"type": "array"
					},
					"description": {
						"type": "string"
					},
					"status": {
						"type": "string",
						"enum": [
							"active",
							"disabled"
						]
					}
				},
				"type": "object",
				"additionalProperties": false
			},
			"WebhookSecretRotated": {
				"properties": {
					"secret": {
						"type": "string",
						"description": "The new secret. Shown once."
					},
					"previousSecretValidUntil": {
						"type": "string",
						"description": "Until then deliveries are signed with the previous secret too."
					}
				},
				"required": [
					"secret",
					"previousSecretValidUntil"
				],
				"type": "object",
				"additionalProperties": false
			},
			"WebhookPing": {
				"properties": {
					"eventId": {
						"type": "string",
						"description": "The id of the webhook.ping event, as it appears in the delivery log."
					}
				},
				"required": [
					"eventId"
				],
				"type": "object",
				"additionalProperties": false
			},
			"WebhookDelivery": {
				"description": "One delivery of one event to one subscription, with its latest attempt.",
				"properties": {
					"id": {
						"type": "string",
						"description": "Stable delivery-log id; each HTTP attempt has its own Senseworks-Delivery-Id."
					},
					"subscriptionId": {
						"type": "string"
					},
					"eventId": {
						"type": "string"
					},
					"eventType": {
						"type": "string"
					},
					"status": {
						"type": "string",
						"enum": [
							"pending",
							"delivering",
							"succeeded",
							"failed",
							"dead"
						]
					},
					"attempt": {
						"type": "number",
						"format": "double",
						"description": "Attempts made so far. At most six before the delivery is dead."
					},
					"nextAttemptAt": {
						"type": "string"
					},
					"lastAttemptAt": {
						"type": "string"
					},
					"lastStatus": {
						"type": "number",
						"format": "double",
						"description": "The HTTP status the receiver answered with."
					},
					"lastError": {
						"type": "string"
					},
					"responseExcerpt": {
						"type": "string",
						"description": "The start of the receiver's answer, at most 512 characters."
					},
					"replayOf": {
						"type": "string",
						"description": "The delivery this one replays."
					},
					"createdAt": {
						"type": "string"
					}
				},
				"required": [
					"id",
					"subscriptionId",
					"eventId",
					"eventType",
					"status",
					"attempt",
					"createdAt"
				],
				"type": "object",
				"additionalProperties": false
			},
			"WebhookDeliveryPage": {
				"properties": {
					"items": {
						"items": {
							"$ref": "#/components/schemas/WebhookDelivery"
						},
						"type": "array"
					},
					"nextCursor": {
						"type": "string"
					}
				},
				"required": [
					"items"
				],
				"type": "object",
				"additionalProperties": false
			},
			"LocalizedText": {
				"properties": {
					"sv": {
						"type": "string"
					},
					"en": {
						"type": "string"
					}
				},
				"required": [
					"sv",
					"en"
				],
				"type": "object",
				"additionalProperties": false
			},
			"Notification": {
				"description": "A notification about something that happened in Senseworks, written for\na person to read. Both languages are always present.",
				"properties": {
					"id": {
						"type": "string"
					},
					"eventId": {
						"type": "string",
						"description": "The event the notification is about, the same id as in webhook deliveries."
					},
					"eventType": {
						"type": "string",
						"description": "For example evidence.attached."
					},
					"level": {
						"type": "string",
						"enum": [
							"info",
							"warning",
							"action"
						],
						"description": "\"action\" means someone needs to do something."
					},
					"audience": {
						"type": "string",
						"enum": [
							"agency",
							"consumer"
						],
						"description": "The agency's staff, or the agency's client."
					},
					"companyDurableKey": {
						"type": "string"
					},
					"subject": {
						"$ref": "#/components/schemas/LocalizedText"
					},
					"body": {
						"$ref": "#/components/schemas/LocalizedText"
					},
					"link": {
						"type": "string",
						"description": "A link to the place in Senseworks the notification is about."
					},
					"createdAt": {
						"type": "string",
						"description": "When the notification was created, RFC 3339 UTC."
					},
					"readAt": {
						"type": "string",
						"description": "When it was marked as read, or absent while unread."
					}
				},
				"required": [
					"id",
					"eventType",
					"level",
					"audience",
					"subject",
					"body",
					"createdAt"
				],
				"type": "object",
				"additionalProperties": false
			},
			"NotificationPage": {
				"description": "One page of notifications, newest first.",
				"properties": {
					"items": {
						"items": {
							"$ref": "#/components/schemas/Notification"
						},
						"type": "array"
					},
					"nextCursor": {
						"type": "string",
						"description": "Pass as `cursor` to get the next page. Absent on the last page."
					}
				},
				"required": [
					"items"
				],
				"type": "object",
				"additionalProperties": false
			},
			"MarkReadResult": {
				"properties": {
					"updated": {
						"type": "number",
						"format": "double",
						"description": "How many notifications changed from unread to read."
					}
				},
				"required": [
					"updated"
				],
				"type": "object",
				"additionalProperties": false
			},
			"MarkReadRequest": {
				"description": "Which notifications to mark as read: exactly one of `ids` or `before`.",
				"properties": {
					"ids": {
						"items": {
							"type": "string"
						},
						"type": "array",
						"description": "At most 200 notification ids."
					},
					"before": {
						"type": "string",
						"description": "Everything created before this time, RFC 3339."
					}
				},
				"type": "object",
				"additionalProperties": false
			},
			"EvidenceTargetProcedure": {
				"properties": {
					"procedureId": {
						"type": "string"
					},
					"name": {
						"type": "string"
					}
				},
				"required": [
					"procedureId",
					"name"
				],
				"type": "object",
				"additionalProperties": false
			},
			"EvidenceTarget": {
				"properties": {
					"checklistId": {
						"type": "string",
						"description": "Pass as checklistId when placing evidence."
					},
					"name": {
						"type": "string"
					},
					"kind": {
						"type": "string",
						"enum": [
							"accountGroup",
							"checklist"
						],
						"description": "\"accountGroup\" for an account group's checklist under Granskning."
					},
					"accountGroupReference": {
						"type": "string"
					},
					"accountNumbers": {
						"items": {
							"type": "string"
						},
						"type": "array",
						"description": "The accounts whose accountNumber resolves to this checklist."
					},
					"procedures": {
						"items": {
							"$ref": "#/components/schemas/EvidenceTargetProcedure"
						},
						"type": "array",
						"description": "Active procedures in the checklist. Pass as procedureId."
					}
				},
				"required": [
					"checklistId",
					"name",
					"kind",
					"procedures"
				],
				"type": "object",
				"additionalProperties": false
			},
			"EvidenceTargets": {
				"description": "The places evidence can be put in an engagement's audit file.",
				"properties": {
					"auditAssignmentId": {
						"type": "string"
					},
					"financialYearId": {
						"type": "string"
					},
					"targets": {
						"items": {
							"$ref": "#/components/schemas/EvidenceTarget"
						},
						"type": "array"
					}
				},
				"required": [
					"auditAssignmentId",
					"financialYearId",
					"targets"
				],
				"type": "object",
				"additionalProperties": false
			},
			"UUIDmd5": {
				"type": "string",
				"format": "uuid",
				"description": "Stringified UUID-like-MD5 hash",
				"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
			},
			"CompanyDurableKey": {
				"$ref": "#/components/schemas/UUIDmd5"
			},
			"FinancialYearId": {
				"$ref": "#/components/schemas/UUIDmd5"
			},
			"EvidencePlacement": {
				"description": "Where in the audit file the document was placed, and how it was resolved.",
				"properties": {
					"resolvedBy": {
						"type": "string",
						"enum": [
							"accountNumber",
							"checklistId",
							"procedureId"
						]
					},
					"checklistId": {
						"type": "string",
						"description": "The checklist the document sits in or under."
					},
					"procedureId": {
						"type": "string",
						"description": "The procedure the document sits under, when one was targeted."
					},
					"accountGroupReference": {
						"type": "string",
						"description": "The account group the account number resolved to."
					}
				},
				"required": [
					"resolvedBy"
				],
				"type": "object",
				"additionalProperties": false
			},
			"Evidence": {
				"description": "Evidence placed in an audit file (revisionsakten) through the API.",
				"properties": {
					"documentId": {
						"type": "string",
						"description": "The document created in the engagement."
					},
					"fileName": {
						"type": "string"
					},
					"auditAssignmentId": {
						"type": "string"
					},
					"financialYearId": {
						"type": "string"
					},
					"placement": {
						"$ref": "#/components/schemas/EvidencePlacement"
					},
					"sourceSystem": {
						"type": "string",
						"description": "The partner's system, echoed back."
					},
					"sourceReference": {
						"type": "string",
						"description": "The partner's own reference for the file, echoed back."
					}
				},
				"required": [
					"documentId",
					"fileName",
					"auditAssignmentId",
					"financialYearId",
					"placement"
				],
				"type": "object",
				"additionalProperties": false
			}
		},
		"securitySchemes": {
			"bearer": {
				"type": "http",
				"scheme": "bearer",
				"bearerFormat": "JWT",
				"description": "An access token from the token endpoint, sent as Authorization: Bearer <token>."
			}
		}
	},
	"info": {
		"title": "Senseworks-API",
		"version": "v2",
		"x-logo": {
			"url": "https://cdn.prod.website-files.com/652053ce99309414b4371553/67d3f80b311995427a0831f0_Api_Black.png",
			"altText": "S-API logo"
		},
		"description": "Version 2 of the Senseworks API. It sits next to version 1, which does not change. Everything here is under `https://api.senseworks.app/v2`.\n\n# Authentication\n\nGet an access token from the version 1 token endpoint with your client credentials, and send it on every call:\n\n```bash\nTOKEN=$(curl --fail-with-body --silent --show-error https://api.senseworks.app/v1/token \\\n  -H \"Content-Type: application/x-www-form-urlencoded\" \\\n  --data-urlencode \"client_id=$CLIENT_ID\" \\\n  --data-urlencode \"client_secret=$CLIENT_SECRET\" | jq -er .access_token)\n\ncurl -H \"Authorization: Bearer $TOKEN\" https://api.senseworks.app/v2/...\n```\n\nThe client's default organisation is used unless you send `X-Tenant-Id` or the `tenantId` query parameter. The header takes precedence. Access must be granted for both the organisation and the endpoint's scope:\n\n| Resource | Scope |\n|---|---|\n| Evidence | `auditdocuments` |\n| Webhooks | `webhooks` |\n| Notifications | `notifications` |\n\nEach resource added to this API adds its row.\n\n# Errors\n\nEvery error is `application/problem+json` with a stable `errorCode` to branch on. `title` and `detail` are for people and may change. The table covers authentication, evidence-placement, rate-limit and availability errors. Treat one you do not know by its status.\n\n```json\n{\n  \"type\": \"https://api.senseworks.app/problems/placement_ambiguous\",\n  \"title\": \"The account resolves to more than one checklist\",\n  \"status\": 409,\n  \"errorCode\": \"placement_ambiguous\",\n  \"detail\": \"Account 1630 belongs to 2 account groups with a checklist. Retry with checklistId.\",\n  \"candidates\": [{ \"checklistId\": \"…\", \"name\": \"15. Kundfordringar\", \"accountGroupReference\": \"…\" }]\n}\n```\n\n| errorCode | Status | What to do |\n|---|---|---|\n| `placement_missing`, `placement_conflicting` | 400 | Send exactly one of `accountNumber` and `checklistId`, optionally with `procedureId`, or `procedureId` alone |\n| `idempotency_key_missing`, `file_missing` | 400 | Send the header or the file |\n| `multipart_invalid` | 400 | The multipart body could not be read. Check the encoding and the field names |\n| `invalid_authentication` | 400 | Check the authentication configuration and retry with valid credentials |\n| `invalid_token` | 401 | Get a new token |\n| `insufficient_scope` | 403 | Your client has no access to that organisation or scope. Ask the organisation's administrator |\n| `evidence_direct_placement_disabled` | 403 | The organisation has not allowed evidence from the API into its audit files |\n| `endpoint_not_found` | 404 | Check the URL and the API version; this route does not exist |\n| `not_found` | 404 | The company, financial year or engagement does not exist, or your client cannot see it |\n| `endpoint_sunset` | 410 | The endpoint has been removed. The `Link` header points to the migration guide |\n| `placement_ambiguous` | 409 | Pick one of `candidates` and retry with its `checklistId` |\n| `assignment_ambiguous` | 409 | Retry with `auditAssignmentId` from `candidates` |\n| `assignment_not_open` | 409 | The engagement is locked or archived. Nothing can be added |\n| `document_name_taken` | 409 | The engagement already has a document with that name. Rename the file |\n| `idempotency_request_in_progress` | 409 | A request with that key is still running. Retry shortly |\n| `placement_not_found` | 422 | The account has no available checklist target in this engagement. Choose a target from evidence-targets or ask the organisation's administrator |\n| `account_not_found`, `checklist_not_found`, `procedure_not_found`, `procedure_not_in_checklist` | 422 | The account, checklist or procedure is not in this engagement |\n| `assignment_not_found`, `assignment_has_no_akt` | 422 | There is no audit engagement with an audit file for that financial year |\n| `assignment_financial_year_mismatch` | 422 | The `auditAssignmentId` belongs to another financial year |\n| `placement_not_in_akt` | 422 | The checklist or procedure exists but has no folder in the audit file |\n| `audit_account_plan_missing` | 422 | The company has no audit account plan, so account numbers cannot be resolved. Use `checklistId` |\n| `validation_failed` | 422 | A field has the wrong type or format. `fields` says which |\n| `idempotency_key_reused` | 422 | The key was used for other content. Use a new key for a new document |\n| `payload_too_large` | 413 | Files up to 100 MB |\n| `rate_limited` | 429 | Too many requests from your IP address in the current window. Wait the number of seconds in `Retry-After`, then retry. `RateLimit` and `RateLimit-Policy` show the quota |\n| `request_failed` | 4xx or 5xx | Use the HTTP status to distinguish an invalid request from a server failure. `detail` says why |\n| `internal_error` | 500 | Retry later. If it persists, contact support with the time of the request |\n| `upstream_failed` | 502 | Retry, and for a POST with the same `Idempotency-Key` |\n| `evidence_placement_setting_unavailable` | 502 | Retry later. The organisation's direct-placement setting could not be verified |\n| `partner_platform_disabled` | 503 | Evidence, webhooks and notifications are not enabled in this environment. Retrying does not help; contact Senseworks support |\n\n# Placing evidence\n\nAn organisation admin must enable direct API evidence placement for the organisation before a partner can write. Until then a write gets `403` with `evidence_direct_placement_disabled`.\n\nAn audit file (revisionsakt) is a tree of folders: one per checklist and one per procedure. Use the evidence-targets endpoint to discover available checklists and procedures for a financial year. An account number resolves only to a target that exists in that audit file.\n\n**1. See where things can go.** `GET /v2/companies/{companyDurableKey}/financial-years/{financialYearId}/evidence-targets` lists every checklist and procedure you can place evidence in. Account group checklists list the account numbers that land there.\n\n**2. Place the document.** `POST /v2/companies/{companyDurableKey}/evidence`, multipart, with the file and one of:\n\n- `accountNumber`: the account the evidence supports. It lands in that account group's checklist.\n- `checklistId`: the checklist, taken from the targets.\n- `procedureId`: a procedure, alone or together with one of the two above.\n\n```bash\ncurl -X POST \"https://api.senseworks.app/v2/companies/$COMPANY/evidence\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Idempotency-Key: example-2026-12-bank-statement\" \\\n  -F \"file=@Bankbesked december.pdf\" \\\n  -F \"financialYearId=$FINANCIAL_YEAR\" \\\n  -F \"accountNumber=1930\" \\\n  -F \"sourceSystem=example-erp\" \\\n  -F \"sourceReference=client-1001/2026-12/bank-statement.pdf\"\n```\n\nThe answer is `201` with the document id and where it was placed. The server never guesses: an account that belongs to two groups with a checklist gives `409` with both as candidates, and an account with no available checklist gives `422`.\n\n# Idempotency\n\nEvery evidence request needs an `Idempotency-Key` header, unique per document and at most 255 characters. A stable value derived from your own reference for the file works well.\n\n- Retry with the same key and the same content: you get the first answer back and nothing is added twice. If the first attempt failed half way, the retry finishes it.\n- The same key with different content is refused (`idempotency_key_reused`).\n- Keys are kept for 24 hours.\n- Keys are stored persistently and shared across API instances. Retrying after a restart uses the stored result or resumes recorded progress. Keep the same key and request content when retrying. After the 24-hour retention window, the request is treated as new.\n\n# Events\n\nCreate a subscription with `POST /v2/webhooks/subscriptions` (scope `webhooks`), and events of the types it names are delivered to its URL. The create takes an `Idempotency-Key` like evidence does: for 24 hours, a retry with the same key and body returns the original subscription and its signing secret (or `409` `idempotency_receipt_unavailable` if Senseworks replaced the key that protects it). After 24 hours the key is forgotten and the same request creates another subscription. `GET /v2/webhooks/deliveries` is the delivery log. A delivery is tried six times over about 23 hours before it is dead, and can be replayed. In an environment where the partner platform is not enabled, the webhook endpoints answer `503` with `partner_platform_disabled`.\n\nEvery event has the same envelope, `https://api.senseworks.app/v2/schemas/event-envelope-v1.json`, and each type's `data` has its own schema under `/v2/schemas/events/`, starting with `evidence.attached-v1.json`. Deduplicate on `id`, which is the same across redeliveries. `notification` carries a Swedish and an English text you can show to people.\n\nEach delivery is signed:\n\n```\nSenseworks-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa…\n```\n\n`v1` is HMAC-SHA256 of `t`, a full stop and the raw request body, with your webhook secret. During a key rotation there are two `v1` values; accept the request if either matches. Reject a `t` more than five minutes old. Compare in constant time, and verify before you parse the body.\n\n```js\nconst crypto = require(\"crypto\");\n\nfunction verify(header, rawBody, secret, now = Date.now() / 1000) {\n  const t = Number(header.match(/(?:^|,)\\s*t=(\\d+)\\s*(?:,|$)/)?.[1]);\n  if (!t || Math.abs(now - t) > 300) return false;\n  const expected = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest();\n  return header\n    .split(\",\")\n    .map((p) => p.trim())\n    .filter((p) => /^v1=[0-9a-f]{64}$/i.test(p))\n    .some((p) => {\n      const given = Buffer.from(p.slice(3), \"hex\");\n      return given.length === expected.length && crypto.timingSafeEqual(given, expected);\n    });\n}\n```\n\n```python\nimport hashlib, hmac, re, time\n\ndef verify(header: str, raw_body: bytes, secret: str) -> bool:\n    parts = [p.strip() for p in header.split(\",\")]\n    t = next((p[2:] for p in parts if p.startswith(\"t=\")), \"\")\n    if not (t.isascii() and t.isdigit()) or abs(time.time() - int(t)) > 300:\n        return False\n    expected = hmac.new(secret.encode(), f\"{t}.\".encode() + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(p[3:], expected) for p in parts if re.fullmatch(r\"v1=[0-9a-f]{64}\", p))\n```\n\nVerify the signature and persist or queue the event before returning `2xx`. Respond promptly and process the queued event asynchronously."
	},
	"paths": {
		"/webhooks/subscriptions": {
			"post": {
				"operationId": "CreateSubscription",
				"responses": {
					"201": {
						"description": "Created",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookSubscriptionCreated"
								}
							}
						}
					},
					"400": {
						"description": "The Idempotency-Key is invalid",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"409": {
						"description": "The Idempotency-Key already created a subscription, but the original response can no longer be returned",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"422": {
						"description": "The Idempotency-Key was reused with another body, or the URL, event type or profile cannot be used",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Creates a subscription. The signing secret is only in the creation\nresponse; subscription reads never return it.\n\nThe URL must be https and resolve to public addresses only.\nSend an Idempotency-Key with every request. For 24 hours, a retry with\nthe same key and body returns the original response, including the\nsigning secret. After that the key is forgotten and the same request\ncreates another subscription.\nThe stored response is encrypted with a server-side key. If Senseworks\nreplaces that key within the 24 hours, the retry gets 409\n`idempotency_receipt_unavailable`. Rotating the subscription's signing\nsecret does not affect the retry.",
				"summary": "Create a webhook subscription",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "header",
						"name": "Idempotency-Key",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/CreateWebhookSubscription"
							}
						}
					}
				}
			},
			"get": {
				"operationId": "ListSubscriptions",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookSubscriptionPage"
								}
							}
						}
					},
					"400": {
						"description": "limit or cursor is not valid (limit_invalid, cursor_invalid)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Lists this client's subscriptions in the organisation, newest first.\nSecrets are never included.",
				"summary": "List webhook subscriptions",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"description": "Page size, 1 to 100. Default 50.",
						"in": "query",
						"name": "limit",
						"required": false,
						"schema": {
							"format": "double",
							"type": "number"
						}
					},
					{
						"description": "nextCursor from the previous page.",
						"in": "query",
						"name": "cursor",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/webhooks/subscriptions/{subscriptionId}": {
			"get": {
				"operationId": "GetSubscription",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookSubscription"
								}
							}
						}
					},
					"404": {
						"description": "No such subscription",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Returns one of this client's subscriptions, without its secret.",
				"summary": "Get a webhook subscription",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "subscriptionId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			},
			"patch": {
				"operationId": "UpdateSubscription",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookSubscription"
								}
							}
						}
					},
					"404": {
						"description": "No such subscription",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"422": {
						"description": "The URL or an event type cannot be used",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Changes the URL, the event types, the description, or turns the\nsubscription off and on.",
				"summary": "Update a webhook subscription",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "subscriptionId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/UpdateWebhookSubscription"
							}
						}
					}
				}
			},
			"delete": {
				"operationId": "DeleteSubscription",
				"responses": {
					"204": {
						"description": "Deleted"
					},
					"404": {
						"description": "No such subscription",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Deletes the subscription and its delivery log.",
				"summary": "Delete a webhook subscription",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "subscriptionId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/webhooks/subscriptions/{subscriptionId}/rotate-secret": {
			"post": {
				"operationId": "RotateSecret",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookSecretRotated"
								}
							}
						}
					},
					"404": {
						"description": "No such subscription",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"409": {
						"description": "The previous rotation is still in its 24-hour grace period",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Replaces the signing secret. For 24 hours deliveries carry a signature\nwith each secret, so the receiver can switch when it is ready.\n\nOnly one previous secret is kept. While it is still in its 24 hours,\nanother rotation is refused with 409 `secret_rotation_in_progress`;\nthe problem detail says when rotating is possible again.",
				"summary": "Rotate the signing secret",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "subscriptionId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/webhooks/subscriptions/{subscriptionId}/ping": {
			"post": {
				"operationId": "PingSubscription",
				"responses": {
					"202": {
						"description": "Queued",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookPing"
								}
							}
						}
					},
					"404": {
						"description": "No such subscription",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"409": {
						"description": "The subscription is disabled",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Sends a `webhook.ping` event to this subscription only. Its delivery\nshows in the delivery log like any other.",
				"summary": "Send a test event",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "subscriptionId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/webhooks/deliveries": {
			"get": {
				"operationId": "ListDeliveries",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookDeliveryPage"
								}
							}
						}
					},
					"400": {
						"description": "limit or cursor is not valid (limit_invalid, cursor_invalid)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"422": {
						"description": "status is not a delivery status (validation_failed)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "The delivery log, newest first. A delivery is tried six times over\nabout 23 hours before it is dead.",
				"summary": "List deliveries",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"description": "Only this subscription's deliveries.",
						"in": "query",
						"name": "subscriptionId",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"description": "pending, delivering, succeeded, failed or dead.",
						"in": "query",
						"name": "status",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"description": "Only deliveries of this event type.",
						"in": "query",
						"name": "eventType",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"description": "Page size, 1 to 100. Default 50.",
						"in": "query",
						"name": "limit",
						"required": false,
						"schema": {
							"format": "double",
							"type": "number"
						}
					},
					{
						"description": "nextCursor from the previous page.",
						"in": "query",
						"name": "cursor",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/webhooks/deliveries/{deliveryId}/replay": {
			"post": {
				"operationId": "ReplayDelivery",
				"responses": {
					"202": {
						"description": "Queued",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/WebhookDelivery"
								}
							}
						}
					},
					"404": {
						"description": "No such delivery",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"409": {
						"description": "The delivery cannot be replayed",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Sends a failed or dead delivery again, as a new delivery of the same\nevent. The event id is the same, so a receiver that already has it can\nignore it.",
				"summary": "Replay a delivery",
				"tags": [
					"Webhooks"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "deliveryId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/notifications": {
			"get": {
				"operationId": "ListNotifications",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/NotificationPage"
								},
								"examples": {
									"Example 1": {
										"value": {
											"items": [
												{
													"id": "3f0c8b52-1d7e-4a9b-8c21-5e6f7a8b9c0d",
													"eventId": "01JG7Z3K2M8Q4V6X9YB0C1D2E3",
													"eventType": "evidence.attached",
													"level": "info",
													"audience": "agency",
													"companyDurableKey": "d7387ff0-5031-45cd-9488-380f966792a6",
													"subject": {
														"sv": "Bevis placerat: Bankbesked.pdf",
														"en": "Evidence placed: Bankbesked.pdf"
													},
													"body": {
														"sv": "Bankbesked.pdf ligger nu under 19. Kassa och bank.",
														"en": "Bankbesked.pdf is now under 19. Kassa och bank."
													},
													"createdAt": "2026-12-14T08:31:07.412Z"
												}
											],
											"nextCursor": "WyIyMDI2LTEyLTE0VDA4OjMxOjA3LjQxMjAwMCIsIjNmMGM4YjUyLTFkN2UtNGE5Yi04YzIxLTVlNmY3YThiOWMwZCJd"
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "limit, cursor, companyDurableKey or since is not valid",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Lists the organisation's notifications, newest first, one page at a\ntime. Pass the returned `nextCursor` as `cursor` for the next page.\nNotifications created while you page appear at the top and do not\nshift the pages after the one you are on.",
				"summary": "List notifications",
				"tags": [
					"Notifications"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"description": "Page size, 1 to 200. Default 50.",
						"in": "query",
						"name": "limit",
						"required": false,
						"schema": {
							"format": "int32",
							"type": "integer"
						}
					},
					{
						"description": "The nextCursor from the previous page.",
						"in": "query",
						"name": "cursor",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"description": "Only notifications about this company.",
						"in": "query",
						"name": "companyDurableKey",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"description": "Only notifications nobody has marked as read.",
						"in": "query",
						"name": "unread",
						"required": false,
						"schema": {
							"type": "boolean"
						}
					},
					{
						"description": "Only notifications created at or after this time, RFC 3339.",
						"in": "query",
						"name": "since",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/notifications/{notificationId}": {
			"get": {
				"operationId": "GetNotification",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Notification"
								},
								"examples": {
									"Example 1": {
										"value": {
											"id": "3f0c8b52-1d7e-4a9b-8c21-5e6f7a8b9c0d",
											"eventId": "01JG7Z3K2M8Q4V6X9YB0C1D2E3",
											"eventType": "evidence.attached",
											"level": "info",
											"audience": "agency",
											"companyDurableKey": "d7387ff0-5031-45cd-9488-380f966792a6",
											"subject": {
												"sv": "Bevis placerat: Bankbesked.pdf",
												"en": "Evidence placed: Bankbesked.pdf"
											},
											"body": {
												"sv": "Bankbesked.pdf ligger nu under 19. Kassa och bank.",
												"en": "Bankbesked.pdf is now under 19. Kassa och bank."
											},
											"createdAt": "2026-12-14T08:31:07.412Z"
										}
									}
								}
							}
						}
					},
					"404": {
						"description": "No such notification in this organisation",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Returns one notification in the organisation. A notification in another\norganisation answers 404, as if it did not exist.",
				"summary": "Get a notification",
				"tags": [
					"Notifications"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "notificationId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/notifications/{notificationId}/read": {
			"post": {
				"operationId": "MarkNotificationRead",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Notification"
								}
							}
						}
					},
					"404": {
						"description": "No such notification in this organisation",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Marks one notification as read. Marking one that is already read\nchanges nothing.",
				"summary": "Mark a notification as read",
				"tags": [
					"Notifications"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "notificationId",
						"required": true,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/notifications/read": {
			"post": {
				"operationId": "MarkNotificationsRead",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/MarkReadResult"
								}
							}
						}
					},
					"400": {
						"description": "Neither or both of ids and before, or too many ids",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Marks several notifications as read: up to 200 `ids`, or everything\ncreated `before` a time. Ids that are not the organisation's are\nignored.",
				"summary": "Mark notifications as read",
				"tags": [
					"Notifications"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [],
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/MarkReadRequest"
							}
						}
					}
				}
			}
		},
		"/companies/{companyDurableKey}/financial-years/{financialYearId}/evidence-targets": {
			"get": {
				"operationId": "GetEvidenceTargets",
				"responses": {
					"200": {
						"description": "Ok",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/EvidenceTargets"
								},
								"examples": {
									"Example 1": {
										"value": {
											"auditAssignmentId": "a1b2c3d4-0028-4000-8000-0000000028a1",
											"financialYearId": "a1b2c3d4-0028-4000-8000-0000000028f1",
											"targets": [
												{
													"checklistId": "a1b2c3d4-0028-4000-8000-0000000028c6",
													"name": "19. Kassa och bank",
													"kind": "accountGroup",
													"accountGroupReference": "63c1f7eb-7f57-4abe-9323-c96bab702367",
													"accountNumbers": [
														"1930"
													],
													"procedures": [
														{
															"procedureId": "a1b2c3d4-0028-4000-8000-0000000028b2",
															"name": "Bankavstämning"
														}
													]
												}
											]
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "The access check for the organisation could not be completed (invalid_authentication)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"401": {
						"description": "The bearer token is missing, invalid or expired",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"403": {
						"description": "Access to this organisation is denied",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"404": {
						"description": "The requested resource was not found",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"409": {
						"description": "More than one engagement for the year",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"422": {
						"description": "No engagement or audit file for the year",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"500": {
						"description": "An unexpected error occurred",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"502": {
						"description": "An upstream service failed",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Lists the places evidence can go in the audit file for a financial year.\n\nEvery checklist and active procedure that has a folder in the audit file\nis listed. Account group checklists under Granskning also carry the\naccount group and the account numbers that resolve to them, so a caller\ncan see in advance where an `accountNumber` lands, and choose a\n`checklistId` when an account belongs to more than one group.",
				"summary": "List evidence placement targets",
				"tags": [
					"Evidence"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "companyDurableKey",
						"required": true,
						"schema": {
							"$ref": "#/components/schemas/CompanyDurableKey"
						}
					},
					{
						"in": "path",
						"name": "financialYearId",
						"required": true,
						"schema": {
							"$ref": "#/components/schemas/FinancialYearId"
						}
					},
					{
						"description": "Only needed when the company has more than one engagement for the year.",
						"in": "query",
						"name": "auditAssignmentId",
						"required": false,
						"schema": {
							"type": "string"
						}
					},
					{
						"in": "query",
						"name": "tenantId",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				]
			}
		},
		"/companies/{companyDurableKey}/evidence": {
			"post": {
				"operationId": "PostEvidence",
				"responses": {
					"201": {
						"description": "Created",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Evidence"
								},
								"examples": {
									"Example 1": {
										"value": {
											"documentId": "5b1c9e2a-7f3d-4c21-9a0e-1f2d3c4b5a69",
											"fileName": "Bankbesked december.pdf",
											"auditAssignmentId": "a1b2c3d4-0028-4000-8000-0000000028a1",
											"financialYearId": "a1b2c3d4-0028-4000-8000-0000000028f1",
											"placement": {
												"resolvedBy": "accountNumber",
												"checklistId": "a1b2c3d4-0028-4000-8000-0000000028c6",
												"accountGroupReference": "63c1f7eb-7f57-4abe-9323-c96bab702367"
											},
											"sourceSystem": "example-erp",
											"sourceReference": "client-1001/2026-12/bank-statement.pdf"
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "The placement, Idempotency-Key, file or multipart body is missing or invalid, or the access check could not be completed (invalid_authentication)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"401": {
						"description": "Authentication is missing or invalid",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"403": {
						"description": "Evidence cannot be placed directly in this organisation's audit files",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"404": {
						"description": "The requested resource was not found",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"409": {
						"description": "The placement is ambiguous, or the Idempotency-Key is in use",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"413": {
						"description": "The uploaded file exceeds 100 MB",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"422": {
						"description": "The placement has no target",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"429": {
						"description": "Too many requests from this IP address (rate_limited). Retry after the seconds in Retry-After",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						},
						"headers": {
							"RateLimit-Policy": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"RateLimit": {
								"schema": {
									"type": "string"
								},
								"required": true
							},
							"Retry-After": {
								"schema": {
									"type": "string"
								},
								"required": true
							}
						}
					},
					"500": {
						"description": "An unexpected error occurred",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"502": {
						"description": "An upstream service could not verify the direct-placement setting or complete the evidence placement",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					},
					"503": {
						"description": "The partner platform is not enabled in this environment (partner_platform_disabled)",
						"content": {
							"application/problem+json": {
								"schema": {
									"$ref": "#/components/schemas/Problem"
								}
							}
						}
					}
				},
				"description": "Places a document as evidence in the audit file for a financial year.\n\nThe place is given in one of three ways. `accountNumber` resolves to the\navailable checklist of the account's group under Granskning.\n`checklistId` names the checklist directly. `procedureId` names a\nprocedure, and can be combined\nwith either of the other two to place the document under a procedure in\nthat checklist.\n\nThe server never guesses. An account that belongs to more than one\ngroup with a checklist gives 409 with the candidates, and an account\nwith no available checklist gives 422.\n\nEvery request needs an `Idempotency-Key`. A repeat with the same key and\nthe same content returns the first response and adds nothing.",
				"summary": "Place evidence in the audit file",
				"tags": [
					"Evidence"
				],
				"security": [
					{
						"bearer": []
					}
				],
				"parameters": [
					{
						"in": "path",
						"name": "companyDurableKey",
						"required": true,
						"schema": {
							"$ref": "#/components/schemas/CompanyDurableKey"
						}
					},
					{
						"description": "Unique per document, chosen by the caller.",
						"in": "header",
						"name": "Idempotency-Key",
						"required": true,
						"schema": {
							"type": "string"
						}
					},
					{
						"in": "query",
						"name": "tenantId",
						"required": false,
						"schema": {
							"type": "string"
						}
					}
				],
				"requestBody": {
					"required": true,
					"content": {
						"multipart/form-data": {
							"schema": {
								"type": "object",
								"properties": {
									"file": {
										"type": "string",
										"format": "binary"
									},
									"financialYearId": {
										"type": "string",
										"description": "The financial year the engagement is for."
									},
									"auditAssignmentId": {
										"type": "string",
										"description": "Only needed when the company has more than one engagement for the year."
									},
									"accountNumber": {
										"type": "string",
										"description": "The account the evidence supports, for example 1930."
									},
									"checklistId": {
										"type": "string",
										"description": "The checklist to place the evidence in."
									},
									"procedureId": {
										"type": "string",
										"description": "The procedure to place the evidence under."
									},
									"sourceSystem": {
										"type": "string",
										"description": "The caller's system, for example \"example-erp\"."
									},
									"sourceReference": {
										"type": "string",
										"description": "The caller's own reference for the file."
									}
								},
								"required": [
									"file",
									"financialYearId"
								]
							}
						}
					}
				}
			}
		}
	},
	"servers": [
		{
			"url": "https://api.senseworks.app/v2"
		}
	],
	"tags": [
		{
			"name": "Evidence",
			"description": "Place documents as evidence in an audit file. Errors are application/problem+json with a stable errorCode."
		},
		{
			"name": "Webhooks",
			"description": "Subscribe to events and read the delivery log. Deliveries are signed with Senseworks-Signature. Errors are application/problem+json with a stable errorCode."
		},
		{
			"name": "Notifications",
			"description": "Integration notifications for the agency, in Swedish and English, to show in a portal. The pull complement to webhooks."
		}
	],
	"x-tagGroups": [
		{
			"name": "API",
			"tags": [
				"Evidence",
				"Webhooks",
				"Notifications"
			]
		},
		{
			"name": "Organisation and companies",
			"tags": []
		}
	]
}