Case Management
This document details the operation and usage of endpoints for managing cases in the Certificall application.
Case Search
Endpoint: GET /cases
Description
The case search endpoint allows authenticated users to query and retrieve cases registered in the Certificall system, applying various filters to refine results according to specific needs.
Authentication
Access to this endpoint requires valid authentication. Include a JWT (JSON Web Token) in the header of your HTTP request as follows:
Authorization: Bearer <Your_JWT_Token>
HTTP Request
Method: GET
URL: /cases
Query Parameters
- reportToken (String, optional): Report token associated with the cases being searched.
- companyId (Number, optional): Identifier of the company associated with the cases.
- frameId (Number, optional): Identifier of the frame used for the cases.
- hours (Number, optional): Number of hours in the past during which cases were created, maximum 240 hours, default 12 hours.
- withReportToken (Boolean, optional): Indicates whether the report token should be included in the results.
- closed (Boolean, optional): Filters by status. Accepted values:
true(closed cases only) orfalse(open cases only). If the parameter is absent or does not match a valid boolean, the endpoint returns both open AND closed cases. - format (Enum, required): Specifies the response format (
ZIPormetadata), usingRESPONSE_FORMAT. - caseId (Number, optional): Identifier of the case being searched.
Note: specify
hoursif you want to receive cases older than the last 12 hours
Example:
curl -X 'GET' \
'https://admin.certificall.app/certificall/api/cases?format=metadata&hours=48' \
-H 'accept: application/json' \
-H 'Authorization: Bearer xxxx'
Responses
200 OK: Returns a list of cases matching the specified criteria.
Response example for a request with format=metadata:
[
{
"cfRef": "REF123",
"closingDate": "2023-01-01T00:00:00Z",
"createdAt": "2022-12-01T00:00:00Z",
"closed": true,
"frame": "Frame A",
"companyName": "Company XYZ",
"reportId": "TOKEN123",
"caseUrl": "https://certificall.example.com/download/SHARETOKEN123",
"metadata": { "site": "Paris", "zone": "A1" }
}
]
metadata(object): the case's free-form metadata, exactly as supplied on creation — through themetadatafield ofPOST /cases/create, a Certilink or the mobile SDK. The key is absent from cases that carry none. It does not look a case up (usereportToken), but it filters consumption.
400 Bad Request: The request is invalid, usually due to missing or incorrect parameters.
500 Internal Server Error: Internal server error preventing request processing.
Case Creation
Endpoint: POST /cases/create
Description
This API endpoint allows authenticated users to create a new case in the Certificall system. The case can be associated with a specific user or a certilink (Magic Link) for use without authentication.
Authentication
Access to this endpoint requires valid authentication. Include a JWT (JSON Web Token) in the header of your HTTP request as follows:
Authorization: Bearer <Your_JWT_Token>
HTTP Request
Method: POST
URL: /cases/create
Request Body (Payload)
Required parameter:
frameId(Number): Identifier of the frame to use for the case.
Optional parameters:
title(String): Case title.userId(String): Keycloak identifier of the user to associate with the case (exclusive withmagicLinkToken).childCompanyId(Number): ID of the target child company (used only with certilink).magicLinkToken(String): Token of an existing certilink to use (exclusive withuserId).certilinkOptions(Object): Options to automatically create a new certilink.reportToken(String, required): Unique report token for the certilink.maxUses(Number, optional): Maximum number of authorized uses.expirationDate(String, optional): Expiration date (format:yyyy-mm-ddThh:mm:ss.sss+hhmm).
reportToken(String): Report token to associate or create a report.caseContext(Object): Case context (visible data).visibleData(Object): Data visible in the case.beneficiary(Object): Beneficiary information.lastName(String): Last name.firstName(String): First name.address(String): Address.city(String): City.postCode(String): Postal code.email(String): Email address.
issuer(String): Case issuer.photoRef(String): Photo reference.reportRef(String): Report reference.instructions(String): Specific instructions for the user.user(String): User name to display in the PDF.
metadata(Object, optional): the case's free-form metadata, string key-value pairs (20 keys max, 40 characters per key, 500 per value). Returned with the case inGET /casesand in the case webhook.
Request Examples
Example 1: Create a case for a specific user
{
"frameId": 10,
"title": "Technical inspection",
"userId": "user-keycloak-id-123",
"reportToken": "REPORT-2024-001",
"caseContext": {
"visibleData": {
"beneficiary": {
"lastName": "Dupont",
"firstName": "Jean",
"email": "jean.dupont@example.com"
},
"issuer": "ACME Company",
"instructions": "Check the general condition of the equipment"
}
},
"metadata": {
"source": "api",
"version": "1.0"
}
}
Example 2: Create a case with an automatic certilink
{
"frameId": 10,
"title": "External inspection",
"certilinkOptions": {
"reportToken": "REPORT-2024-002",
"maxUses": 5,
"expirationDate": "2025-12-31T23:59:59.000+0100"
},
"caseContext": {
"visibleData": {
"beneficiary": {
"lastName": "Martin",
"firstName": "Sophie",
"address": "123 Rue de la Paix",
"city": "Paris",
"postCode": "75001",
"email": "sophie.martin@example.com"
},
"instructions": "Please note the apartment number"
}
}
}
Example 3: Create a case with an existing certilink
{
"frameId": 10,
"magicLinkToken": "09ca0baf-ce70-47a0-b84e-0b4acb0ecec3",
"title": "Scheduled inspection",
"caseContext": {
"visibleData": {
"issuer": "ABC Company",
"instructions": "Follow the standard protocol"
}
}
}
Responses
Successful response:
- Status:
201 Created - Description: The case has been created successfully.
- Response example:
{
"id": 12345,
"cfRef": "CAS-12345_CMP-1",
"frameId": 10,
"title": "Technical inspection",
"createdAt": "2024-01-15T10:30:00Z",
"closed": false,
"magicLinkUrl": "https://app.certificall.app/?mt=09ca0baf-ce70-47a0-b84e-0b4acb0ecec3"
}
Error response:
-
Status:
400 Bad Request -
Description: The request is invalid (missing or incorrect parameters).
{
"statusCode": 400,
"message": "frameId is required",
"error": "Bad Request"
} -
Status:
403 Forbidden -
Description: You do not have the necessary permissions to create a case.
-
Status:
404 Not Found -
Description: The specified frame does not exist or does not belong to your company.
Required Permissions
- The user must have the
p_apiscope and thecreatepermission on thecaseresource. - The frame must belong to your company or be accessible.
Use Cases
Use this endpoint to:
- Create a case intended for a specific user on your team
- Generate a case accessible via a certilink (Magic Link) for external users
- Initialize a case with pre-filled information (beneficiary, instructions, etc.)
- Create a case and a certilink in a single operation
Important Notes
userIdandmagicLinkTokenare mutually exclusive: you must use one or the other, but not both.- If you use
certilinkOptions, a new certilink will be automatically created for this case. - The
reportTokenfield incertilinkOptionsis required if you use this option. - Metadata must be key-value pairs with string-type values.
Do not call this endpoint in a loop: you would quickly hit the rate limit and get a 429. Use bulk create below, which counts as a single request.
Bulk Create
Endpoint: POST /cases/bulk-create
Creates several cases in a single request. Designed for integrations that need to open a set of cases at once — for instance one case per intervention on the same site.
Only one call is counted, whatever the size of the array. This endpoint has its own rate limit of 10 requests per minute — up to 500 cases created per minute, against 60 when looping on the single creation.
Request
POST /certificall/api/cases/bulk-create
Authorization: Bearer <Your_JWT_Token>
Content-Type: application/json
The body is an array of objects, each in the same format as the single creation (POST /cases/create):
[
{ "frameId": 10, "title": "Intervention batch A", "userMail": "technician@example.com" },
{ "frameId": 10, "title": "Intervention batch B", "userMail": "technician@example.com" },
{ "frameId": 12, "title": "Final check" }
]
Limit: 50 cases per request. Beyond that the API responds 400 — split your processing into several calls.
Two identical lines in the array create two distinct cases. Before resending an interrupted request (network failure, timeout), check with GET /cases which cases were already created.
Required Permissions
Two permissions are required:
| Permission | Purpose |
|---|---|
p_api:case:create | Creating a case |
p_api:case:bulk_create | Access to bulk create |
If either is missing, the API responds 403 and no case is created.
Response
Status code: 200 OK, even if some cases fail. Each case is created independently: one failure does not cancel the others, and cases already created are kept.
{
"state": "Cases bulk create ended",
"date": "2026-09-15T09:12:34.567Z",
"company": 42,
"success": [
{ "index": 0, "caseId": 12345 },
{ "index": 2, "caseId": 12346 }
],
"error": [
{ "index": 1, "error": "Error while creating case" }
]
}
| Field | Description |
|---|---|
success | Cases actually created |
error | Lines not created, each with its reason |
index | Position in the array you sent |
caseId | Identifier assigned to the created case. Absent from error entries, since the case does not exist |
error arrayA 200 does not mean every case was created. Walk through error to find the lines to fix and resend.
Processing order is not guaranteed: rely on index to match each caseId with the corresponding line of your request. Only the identifier is returned; to get the details of a created case, use GET /cases with the caseId parameter.
The failure reason is the one returned by the business service (frame not found, unknown user…) or, by default, Error while creating case. These labels are not guaranteed to be stable: route your errors on a line being present in error[] rather than on the exact wording.
Request errors
In these cases, no case is created:
| Status | Context |
|---|---|
400 | Empty array, more than 50 cases, or invalid element. Errors for every element are returned, prefixed with their position: [1] frameId must be a number conforming to the specified constraints |
403 | One of the two required permissions is missing |
429 | Rate limit of 10 requests per minute exceeded on this endpoint |
Closing a Case
Endpoint: GET /cases/close/:caseId
Description
This API endpoint allows users to close an ongoing case. Once closed, the case is considered finalized and can no longer be modified. This action is essential to indicate the end of the data collection process.
Request
-
URL:
/cases/close/:caseId -
HTTP Method:
GET -
Path parameters:
caseId:number- The unique identifier of the case to close.
-
Required headers:
Authorization: Bearer <Your_JWT_Token>
Responses
Successful response:
- Status:
200 OK - Description: The case has been closed successfully.
- Response example:
{
"status": "Success",
"message": "Case closed successfully"
}
Error response:
-
Status:
400 Bad Request -
Description: The request is invalid or the case does not exist.
{
"statusCode": 400,
"message": "Invalid case ID",
"error": "Bad Request"
} -
Status:
403 Forbidden -
Description: You do not have the necessary permissions or the case does not belong to your company.
Required Permissions
- The case must belong to your company to be closed.
Use Cases
Use this endpoint when you have finished collecting all necessary data for a case and want to finalize it. Once closed, the case can no longer receive new items.
Updating a Case
Endpoint: PATCH /cases/update
Description
This API endpoint allows users to update information of an existing case. You can modify the title, associated frame, user, report token, or case context.
Request
-
URL:
/cases/update -
HTTP Method:
PATCH -
Required headers:
Authorization: Bearer <Your_JWT_Token>
Content-Type: application/json
Request Body (Payload)
caseId(Number, required): Identifier of the case to update.title(String, optional): New case title (maximum 255 characters).frameId(Number, optional): Identifier of the new frame to associate.userId(String, optional): Keycloak identifier of the user to associate.reportToken(String, optional): Report token to associate with the case.caseContext(Object, optional): Case context (visible data).visibleData(Object, optional): Data visible in the case.beneficiary(Object, optional): Beneficiary information.lastName(String): Last name.firstName(String): First name.address(String): Address.city(String): City.postCode(String): Postal code.email(String): Email address.
issuer(String): Case issuer.photoRef(String): Photo reference.reportRef(String): Report reference.instructions(String): Specific instructions.user(String): User name to display.
metadata(Object, optional): the case's free-form metadata, string key-value pairs (20 keys max, 40 characters per key, 500 per value). Replaces the case's current metadata;nullclears it. Returned inGET /casesand in the case webhook.
Request body example:
{
"caseId": 12345,
"title": "Updated inspection",
"reportToken": "REPORT-2024-001",
"caseContext": {
"visibleData": {
"beneficiary": {
"lastName": "Dupont",
"firstName": "Jean",
"email": "jean.dupont@example.com"
},
"issuer": "ACME Company",
"instructions": "Check the general condition of the equipment"
}
},
"metadata": {
"source": "api",
"version": "2.0"
}
}
Responses
Successful response:
- Status Code: 200 OK
- Description: The case has been updated successfully.
Successful response example:
{
"success": true
}
Error response:
-
Status Code: 400 Bad Request
-
Description: The request is invalid (missing or incorrect parameters).
-
Status Code: 403 Forbidden
-
Description: You do not have the necessary permissions or the case does not belong to your company.
-
Status Code: 404 Not Found
-
Description: The specified case does not exist.
Required Permissions
- The case must belong to your company to be updated.
Use Cases
Use this endpoint to:
- Correct a case title
- Modify beneficiary information
- Associate a new report token
- Add or modify metadata
- Change the frame associated with the case
Do not call this endpoint in a loop: you would quickly hit the rate limit and get a 429. Use bulk update below, which counts as a single request.
Bulk Update
Endpoint: PATCH /cases/bulk-update
Updates several cases in a single request. Designed for integrations that need to propagate the same information across a set of related cases — for instance reflecting a date change on every case attached to the same deal.
Only one call is counted, whatever the size of the array. This endpoint has its own rate limit of 10 requests per minute — up to 1000 cases updated per minute, against 60 when looping on the single update.
Request
PATCH /certificall/api/cases/bulk-update
Authorization: Bearer <Your_JWT_Token>
Content-Type: application/json
The body is an array of objects, each in the same format as the single update:
[
{ "caseId": 12345, "scheduledAt": "2026-09-10T09:00:00+02:00" },
{ "caseId": 12346, "scheduledAt": "2026-09-10T09:00:00+02:00" },
{ "caseId": 12347, "scheduledAt": "2026-09-10T09:00:00+02:00" }
]
Limit: 100 cases per request. Beyond that the API responds 400 — split your processing into several calls.
Required Permissions
Two permissions are required:
| Permission | Purpose |
|---|---|
p_api:case:update | Updating a case |
p_api:case:bulk_update | Access to bulk update |
If either is missing, the API responds 403 and no case is processed.
Response
Status code: 200 OK, even if some cases fail. Each case is processed independently: one failure does not cancel the others.
{
"state": "Cases bulk update ended",
"date": "2026-08-13T09:12:34.567Z",
"company": 42,
"success": [
{ "index": 0, "caseId": 12345 },
{ "index": 2, "caseId": 12347 }
],
"error": [
{ "index": 1, "caseId": 12346, "error": "case.notBelongCompany" }
]
}
| Field | Description |
|---|---|
success | Cases actually updated |
error | Failed cases, each with its reason |
index | Position in the array you sent |
caseId | Identifier of the case concerned |
error arrayA 200 does not mean every case was updated. Walk through error to find which ones failed and why.
Processing order is not guaranteed: do not rely on the position within success or error, but on the index field, which points back to the original position in your request.
Common failure reasons
| Message | Cause |
|---|---|
case.notBelongCompany | The case belongs to another company |
case.scheduledAt.mustBeCurrentOrFutureMonth | The scheduled date is earlier than the current month |
case.scheduledAt.invalid | Unrecognised date format |
Error while updating case | The case does not exist, or the business service is temporarily unavailable |
These labels come from the business service and are not guaranteed to be stable. To route your errors, rely on a case being present in error[] rather than on the exact wording.
Deleting a Case
Endpoint: DELETE /cases/delete/:caseId
Description
This API endpoint allows users to permanently delete a case from the system. This action is irreversible and deletes all data associated with the case.
Request
-
URL:
/cases/delete/:caseId -
HTTP Method:
DELETE -
Path parameters:
caseId:number- The unique identifier of the case to delete.
-
Required headers:
Authorization: Bearer <Your_JWT_Token>
Responses
Successful response:
- Status:
200 OK - Description: The case has been deleted successfully.
- Response example:
{
"status": "Success",
"message": "Case deleted successfully"
}
Error response:
-
Status:
400 Bad Request -
Description: The request is invalid or the case does not exist.
-
Status:
403 Forbidden -
Description: You do not have the necessary permissions or the case does not belong to your company.
Required Permissions
- The case must belong to your company to be deleted.
Warning
WARNING: This operation is irreversible. Once the case is deleted, all associated data (items, photos, videos, metadata) will be permanently lost.
Use Cases
Use this endpoint only when you need to permanently delete a case, for example:
- Case created by mistake
- Erroneous data that requires complete deletion
- Compliance with data retention policies
Do not call this endpoint in a loop: you would quickly hit the rate limit and get a 429. Use bulk delete below, which counts as a single request.
Bulk Delete
Endpoint: POST /cases/bulk-delete
Deletes several cases in a single request. Each case is deleted exactly as with the single deletion.
Only one call is counted, whatever the size of the array. This endpoint has its own rate limit of 10 requests per minute — up to 500 cases deleted per minute, against 60 when looping on the single deletion.
Request
POST /certificall/api/cases/bulk-delete
Authorization: Bearer <Your_JWT_Token>
Content-Type: application/json
The body is an object holding the list of identifiers to delete:
{
"caseIds": [12345, 12346, 12347]
}
POST and not DELETE?Many HTTP clients and proxies handle a request body on DELETE poorly. POST guarantees that the list of cases reaches the API intact.
Limit: 50 cases per request, with no duplicates. Beyond that the API responds 400 — split your processing into several calls.
As with the single deletion, all data associated with the deleted cases (items, photos, videos, metadata) is permanently lost. Double-check the list of identifiers before sending it.
Required Permissions
Two permissions are required:
| Permission | Purpose |
|---|---|
p_api:case:delete | Deleting a case |
p_api:case:bulk_delete | Access to bulk delete |
If either is missing, the API responds 403 and no case is deleted.
Response
Status code: 200 OK, even if some cases fail. Each case is processed independently: one failure does not cancel the others.
{
"state": "Cases bulk delete ended",
"date": "2026-09-15T09:12:34.567Z",
"company": 42,
"success": [
{ "index": 0, "caseId": 12345 },
{ "index": 2, "caseId": 12347 }
],
"error": [
{ "index": 1, "caseId": 12346, "error": "Error while deleting case" }
]
}
| Field | Description |
|---|---|
success | Cases actually deleted |
error | Failed cases, each with its reason |
index | Position in the caseIds array you sent |
caseId | Identifier of the case concerned |
error arrayA 200 does not mean every case was deleted. Walk through error to find which ones failed and why.
Processing order is not guaranteed: do not rely on the position within success or error, but on the index field.
The failure reason is the one returned by the business service (case from another company, case not found…) or, by default, Error while deleting case. These labels are not guaranteed to be stable: route your errors on a case being present in error[] rather than on the exact wording.
Request errors
In these cases, no case is deleted:
| Status | Context |
|---|---|
400 | Empty caseIds, more than 50 identifiers, non-numeric identifier, or the same identifier sent several times |
403 | One of the two required permissions is missing |
429 | Rate limit of 10 requests per minute exceeded on this endpoint |
Security and Best Practices
- Ensure that all necessary data has been collected before closing a case.
- Only the fields provided in the update request will be modified. Other fields will retain their current values.
- Consider backing up important data before deletion.
- Always verify that the
caseIdcorresponds to the case you want to manipulate. - Metadata must be key-value pairs with string-type values.
- Interactions with the Certificall API must always be performed via a secure connection (HTTPS).
- Store and manage the authentication token securely.
By following these instructions, you will be able to securely manage your cases via the Certificall API.