>_aprovaAI API Documentation
User guide PT
Documentation for developers

Integration API

Connect an ERP or another system to aprovaAI: create requests from external documents, let aprovaAI's approval engine drive the decision, and query the outcome to reflect it back in the source.

Introduction

The integration API is source-system agnostic — it doesn't assume Protheus, SAP, RM, or any specific ERP. What speaks the language of a specific system is a connector (agent) that you maintain, outside of aprovaAI, translating between your system and this contract.

aprovaAI never initiates a connection to your system. It only receives authenticated calls and responds — fetching data from the source and writing the result back is your connector's responsibility.

A request created through this API behaves like any other from that point on: it goes through the workflow engine configured for the tenant, appears in aprovaAI's listings, generates in-app/push/email notifications for approvers, and has comments and history.

Base URL
https://aprovaai.cyberpolos.net

Authentication

Each integration connection receives an API key in the format <connectionId>.<secret>, shown once when the credential is created — aprovaAI doesn't store or redisplay the secret in plain text after that. Send it on every call as a Bearer token:

Header
Authorization: Bearer cm3xh2j4k0001abcd.k7f9QpX2yT8vR1nZmW3sLd

The credential only sees its own tenant, and can only create requests for types enabled for integration (see requestTypeKey in Create request). During the pilot, credentials are issued by the aprovaAI team — talk to your contact to get yours.

Idempotency

Every created request is identified by the externalSystem + externalRef pair you send. If your connector resends the same creation — due to a retry after a timeout, or reprocessing — aprovaAI never duplicates it: it returns the existing request.

In practice

externalRef is an opaque value to aprovaAI — it can be any JSON that identifies the document at the source (branch, document type, key). It doesn't need to be a single field; it exists only for aprovaAI and is returned unchanged in decision queries.

Errors

Every error response (4xx/5xx) follows the same format, with a stable code for programmatic handling and a readable message:

Error response
{
  "error": {
    "code": "requester_not_found",
    "message": "No user with this email in this tenant."
  }
}

Most common codes, across endpoints:

CodeHTTPMeaning
unauthorized401Missing or malformed Authorization header, or invalid/inactive credential.
invalid_body422Request body doesn't match the expected schema.
request_type_not_found404requestTypeKey doesn't exist (or is inactive) in this tenant.
requester_not_found404No user with the given requesterEmail in this tenant.
invalid_custom_fields422customFields doesn't match the request type's required fields.
already_final409The request is already in a final status (approved/rejected/cancelled).
external_approval_disabled403This credential doesn't have permission to approve via API — see Approval via API.
invalid_cursor422Invalid since parameter in GET /decisions.

Endpoints

POST /api/integrations/requests

Creates a request from an external document. Idempotent — calling it again with the same externalSystem + externalRef returns the existing request instead of duplicating it.

Request body

FieldTypeDescription
externalSystemstringrequiredFree-form identifier of the source, e.g.: "protheus".
externalRefJSONrequiredOpaque value identifying the document at the source — used for idempotency.
requestTypeKeystringrequiredPoints to the integrationKey of an enabled request type.
titlestringrequiredTitle shown in listings and notifications.
descriptionstringoptionalFree text.
amountCentsintegeroptionalValue in cents (e.g., 600000 = R$ 6,000.00) — integer, never a loose decimal: avoids floating point, and type validation already rejects strings/fractions. Triggers the workflow's value rules.
requesterEmailstringrequiredMust match a user that already exists in the tenant.
customFieldsobjectoptionalValues for the dynamic fields configured on the request type.
cURL
curl -X POST https://aprovaai.cyberpolos.net/api/integrations/requests \
  -H "Authorization: Bearer <connectionId>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "externalSystem": "protheus",
    "externalRef": { "branch": "01", "docType": "SC7", "key": "000123" },
    "requestTypeKey": "purchase_order",
    "title": "Purchase Order 000123 — Dell Brazil",
    "amountCents": 600000,
    "requesterEmail": "requester@company.com",
    "customFields": { "vendor": "Dell Brazil", "justification": "Laptops for the team" }
  }'
Response — 201 Created (or 200 if it already existed)
{
  "requestId": "cmr1a2b3c4d5",
  "status": "pending",
  "url": "https://aprovaai.cyberpolos.net/requests/cmr1a2b3c4d5"
}
GET /api/integrations/decisions?since=<cursor>

Lists requests originated by this API whose status changed to a final state since the given cursor. Paginated by opaque cursor, not by date — avoids losing events to timestamp ties.

cURL
curl "https://aprovaai.cyberpolos.net/api/integrations/decisions?since=$CURSOR" \
  -H "Authorization: Bearer <connectionId>.<secret>"
Response
{
  "decisions": [
    {
      "requestId": "cmr1a2b3c4d5",
      "externalSystem": "protheus",
      "externalRef": { "branch": "01", "docType": "SC7", "key": "000123" },
      "status": "approved",
      "decidedAt": "2026-07-19T18:00:00Z",
      "decidedExternally": false,
      "history": [
        { "step": "Manager Approval", "decision": "approved",
          "approverEmail": "manager@company.com", "comment": null }
      ]
    }
  ],
  "nextCursor": "MjAyNi0wNy0xOVQxODowMDowMC4wMDBafGNtcjFhMmIzYzRkNQ"
}
  • Only reports final states: approved, rejected, cancelled. changes_requested doesn't appear here — it's only visible inside aprovaAI.
  • When there's nothing new, the response is decisions: [] with the same nextCursor received — never null.
  • decidedExternally: true means this decision came from your own POST .../status — don't write it back to the source, to avoid a loop.
  • Persist the nextCursor locally between calls. Without since, the listing starts from the beginning.
GET /api/integrations/health

Confirms that the credential and tenant are active. No side effects — free for your connector to validate connectivity before operating.

Response
{ "ok": true, "tenant": "demo-company", "connection": "Production ERP" }
POST /api/integrations/requests/{requestId}/status

Applies a status decided outside aprovaAI's internal workflow — for when the source document changes state in your system after it's already been synced. It's an exception valve: the normal path is still approvers deciding inside aprovaAI.

FieldTypeDescription
statusstringrequiredcancelled, rejected, or approved.
reasonstringrequired**Required for rejected and approved; optional for cancelled.
commentstringoptionalIf filled in, posts a message in the request's chat (visible to whoever is following it).
cURL
curl -X POST https://aprovaai.cyberpolos.net/api/integrations/requests/cmr1a2b3c4d5/status \
  -H "Authorization: Bearer <connectionId>.<secret>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "cancelled",
    "reason": "Order cancelled by the buyer directly in Protheus"
  }'
Response — 200 OK
{ "requestId": "cmr1a2b3c4d5", "status": "cancelled" }

Rules

  • Only applies to requests originated by this API, in a non-final status (draft/pending). Already final → 409 already_final.
  • cancelled and rejected are always allowed.
  • approved requires the credential to have explicit permission — see Approval via API below.

Approval via API

Approving a request without going through aprovaAI's named approvers is a governance decision, not just a technical one — by default, this credential cannot do that. allowExternalApproval = false

When explicitly enabled for a credential, an approval sent via POST .../status still counts as approved in all flows and reports, but is flagged as decidedExternally: true and visually highlighted in aprovaAI's interface — distinct from an approval that went through the workflow's approvers.

When to use

Use it to reflect an approval that already actually happened in the source system (e.g., someone with authority approved directly in the ERP), not as a shortcut to skip aprovaAI's workflow. If your credential needs this behavior, ask the aprovaAI team to enable it.

Recommended integration flow

  1. Store the API key you receive securely — it isn't shown again.
  2. Translate each document from your system into the POST /requests payload (how fields map is specific to your source system).
  3. Store locally the relationship between the document's key at the source and the returned requestId.
  4. Poll GET /decisions periodically, persisting the nextCursor on every call.
  5. Apply the decision back in your system, with your own retry logic — aprovaAI doesn't know whether that write succeeded.
  6. If the source document is cancelled, denied, or approved outside aprovaAI after being synced, call POST .../status to reflect it — without this step, the request stays pending indefinitely.