Fincore eSign API v1

https://esign.fincoreerp.com/api/v1

REST API for integration-mode tenants (PLAN.md §7). Every recipient-facing email still sends through the tenant's own connected sender (§6.1); this API only orchestrates envelopes. **Auth:** every request carries `Authorization: Bearer <api key>`. Keys are prefixed `fes_live_...` or `fes_test_...` - test-mode keys never consume document quota and are intended for sandbox integration testing before pointing production traffic at this API. **Idempotency:** `POST /v1/envelopes` and `POST /v1/envelopes/{id}/send` accept an `Idempotency-Key` header. A retried request with the same key returns the exact original response instead of re-running the operation (and, for `/send`, without double-consuming quota). A concurrent duplicate (the original request hasn't finished yet) returns `409`. **Rate limiting:** enforced per API key. A `429` response includes `Retry-After`. **Errors:** every error body is `{ "error": string, "code"?: string }`. `code` is present for machine-distinguishable conditions - notably `quota_exhausted` (this tenant's document quota for the current period is used up; top up or wait for renewal), `license_inactive` (no active license on file with Fincore - contact your account representative), and `byo_required` (the tenant's trial email period has ended and no BYO Resend account is connected - sending is blocked until one is).

Endpoints

post/documents

Upload a PDF to reference when creating an envelope

Request body (multipart/form-data)
{
  "type": "object",
  "required": [
    "file"
  ],
  "properties": {
    "file": {
      "type": "string",
      "format": "binary"
    }
  }
}

201 - Uploaded

Response schema (application/json)
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    }
  }
}

400 - Missing or invalid file

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

post/envelopes

Create a draft envelope

Request body (application/json)
{
  "type": "object",
  "required": [
    "documentId",
    "title",
    "recipients"
  ],
  "properties": {
    "documentId": {
      "type": "string",
      "format": "uuid"
    },
    "title": {
      "type": "string"
    },
    "message": {
      "type": "string"
    },
    "signingMode": {
      "type": "string",
      "enum": [
        "sequential",
        "parallel"
      ],
      "default": "sequential"
    },
    "expiresInDays": {
      "type": "integer"
    },
    "reminderIntervalDays": {
      "type": "integer",
      "minimum": 1,
      "maximum": 30,
      "description": "Days between reminder emails while pending. Omit for a single pre-expiry reminder."
    },
    "externalReference": {
      "type": "string"
    },
    "recipients": {
      "type": "array",
      "minItems": 1,
      "maxItems": 10,
      "items": {
        "$ref": "#/components/schemas/Recipient"
      }
    },
    "fields": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Field"
      }
    }
  }
}

201 - Created

Response schema (application/json)
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft"
      ]
    }
  }
}

400 - Validation error

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

409 - A request with this Idempotency-Key is still in flight

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

get/envelopes/{id}

Get envelope status and per-recipient state

200 - OK

Response schema (application/json)
{
  "$ref": "#/components/schemas/Envelope"
}

404 - Not found

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

post/envelopes/{id}/send

Send a draft envelope to its first recipient(s)

200 - Sent

Response schema (application/json)
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "status": {
      "type": "string",
      "enum": [
        "pending"
      ]
    }
  }
}

402 - Quota exhausted, or the tenant's BYO email connection needs attention

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

403 - Sending is blocked for this tenant

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

409 - A request with this Idempotency-Key is still in flight

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

post/envelopes/{id}/void

Cancel an outstanding (draft or pending) envelope

200 - Voided

Response schema (application/json)
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "status": {
      "type": "string",
      "enum": [
        "voided"
      ]
    }
  }
}

400 - Envelope is already completed/declined/voided/expired

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

get/envelopes/{id}/download

Get short-lived download URLs for the completed signed document + certificate

200 - OK - URLs expire in 10 minutes

Response schema (application/json)
{
  "type": "object",
  "properties": {
    "documentUrl": {
      "type": "string",
      "format": "uri"
    },
    "certificateUrl": {
      "type": "string",
      "format": "uri",
      "nullable": true
    },
    "expiresInSeconds": {
      "type": "integer"
    }
  }
}

409 - Envelope is not completed yet

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

410 - This envelope's content has been deleted (§14.4 retention)

Response schema (application/json)
{
  "$ref": "#/components/schemas/Error"
}

get/usage

Current quota/usage for the calling tenant

200 - OK

Response schema (application/json)
{
  "type": "object",
  "properties": {
    "totalQuota": {
      "type": "integer",
      "nullable": true,
      "description": "null means unlimited (no document cap on the current plan)."
    },
    "totalUsed": {
      "type": "integer"
    },
    "totalRemaining": {
      "type": "integer",
      "nullable": true,
      "description": "null means unlimited - see totalQuota."
    },
    "licenses": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string"
          },
          "documentQuota": {
            "type": "integer",
            "nullable": true
          },
          "used": {
            "type": "integer"
          },
          "remaining": {
            "type": "integer",
            "nullable": true
          },
          "periodEnd": {
            "type": "string",
            "format": "date"
          }
        }
      }
    }
  }
}