Skip to content

Agreements – Deal Creation

When should I use this?

  • After the user has authenticated with PAYCIFI, completed wallet linking, and finished profile completion, when a payer is ready to formalize scope, participants, fees, and deadlines for a programmable escrow.
  • Before running any funding challenges or participant acceptance workflows, because those steps depend on the agreement ID generated here. Wallets that are not authenticated through PAYCIFI cannot call POST /agreements, but they can still participate later on-chain once they are invited and accept via the supported flows.

Purpose

Create a programmable deal that binds the payer, service providers, and optional arbitrator around defined tasks, amounts, and deadlines. The backend stores the agreement, provisions invitations, and mirrors the payload on-chain; from that point, acceptance, validation, funding, arbitration, and payouts are enforced directly by the DShare smart contract while the backend supplies orchestration utilities and status reads.

Preconditions

  • Authentication: Caller MUST include a valid Authorization: Bearer <accessToken> header (obtained after login). Only PAYCIFI-issued tokens are accepted; unauthenticated external wallets—even if they control valid Ethereum or Circle addresses—cannot invoke this endpoint.
  • Account state: The creator’s profile MUST be complete (POST /users/register done) and wallet setup finished so payer funding can occur later.
  • Participants & creator role: Payload MUST include at least one payer and one provider. Exactly one participant MUST have owner: true, and agreementInfo.ownerUserId MUST match that participant’s userId and the authenticated user. The owner can be either the payer or a provider—the backend only checks the owner flag + ownerUserId pair when computing the on-chain creator ID.
  • Arbitrator (optional): Arbitration remains free-form: send name, email, walletAddress, and optionally phone/additionalInfo. If the email already exists on a non-arbitrator user, the backend aborts creation with agreement-creation-service-error; otherwise it either reuses or auto-registers the arbitrator and sends an invitation.

Client Responsibilities

  • MUST provide normalized lowercase emails for all participants and arbitrator data.
  • MUST ensure agreementInfo.ownerUserId equals both the authenticated user ID and the participant flagged with owner: true.
  • MUST supply acceptanceDeadline and completionDeadline as UNIX timestamps expressed in seconds.
  • MUST choose a supported currency code defined by PAYCIFI (agreementInfo.currency).
  • SHOULD ensure every agreementItems entry references an existing participant ID and includes the required pricing fields.

API Flow

  1. Client → POST /agreements with { participants, agreementInfo, agreementItems, arbitrationSettings }.
  2. Validation: Backend normalizes emails, checks required arrays, validates currency and deadlines, and calculates platform fees via CalculFees.
  3. Persistence: AgreementsService.createAgreement inserts the agreement with status pending, stores participants (payer/provider/arbitrator) with agreement_status = pending, and creates items. Invitations are queued for anyone without userId.
  4. Blockchain provisioning: Controller calls BlockchainService.createAgreement, mirroring totals, fee strategy, partners, and deadlines on-chain. Returning data (contract address, token address, RPC URL, block number) is written back to the agreement record.
  5. Notifications: Email invitations go to every pending participant plus optional arbitrator, and a confirmation email goes to the creator.
  6. Response: API returns success: true, database snapshot (agreementInfo, participants, agreementItems), computed totals, agreementId, and blockchainAgreement metadata. Every participant remains in pending state until they accept and the payer funds the agreement.

Creator role & ownership

  • The controller identifies the creator by locating the participant flagged with owner: true. No participant-type restriction exists: either the payer or a provider can be the owner.
  • agreementInfo.ownerUserId MUST match that participant’s userId. Mismatches are not auto-corrected and will cause downstream blockchain calls to fail, so the client MUST enforce this alignment before calling the API.

Arbitrator handling

  • Arbitrators are optional but MUST already exist as arbitrators in PAYCIFI. Select them using the arbitrator discovery endpoints (GET /arbitrators?isCurated=... or GET /arbitrators/availability).
  • During creation, the backend validates that the provided email maps to an active arbitrator. If the email exists but is not flagged as arbitrator, agreement-creation-service-error is returned and the transaction is rolled back.
  • Integrators SHOULD never assume arbitrary emails can be promoted to arbitrator status via this endpoint; pre-select and validate the arbitrator before submitting the agreement payload.

Arbitrator Discovery

Use the dedicated arbitrator endpoint to fetch curated (PAYCIFI-approved) or non-curated arbitrators before calling POST /agreements.

  • Endpoint: GET /arbitrators?isCurated=true|false
  • Auth: Requires the standard Authorization: Bearer <accessToken> header.
  • Query param: isCurated is mandatory. Set it to true to list curated arbitrators that PAYCIFI already validated, or false to list all non-curated arbitrators in your workspace.
  • Response: Returns sanitized arbitrator objects { id, name, email, isAvailable } that you can use to pre-fill arbitrationSettings.
  • Availability check: To confirm whether a known arbitrator email is currently available (without listing), call GET /arbitrators/availability?email=<email>; it returns { success, available, arbitrator }.

Example request

GET /arbitrators?isCurated=true
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Example response

[
  {
    "id": "23c44f2e-865e-4c2f-8f85-2d3c5d2a9e11",
    "name": "DShare Arbitration Desk",
    "email": "panel@paycifi.com",
    "isAvailable": true
  },
  {
    "id": "6a89e5fd-9342-4dbe-8b39-2064c3914ea4",
    "name": "Global Escrow Partners",
    "email": "team@global-escrow.io",
    "isAvailable": false
  }
]

If no arbitrator matches the requested curation level or availability filters, the endpoint returns an empty array. When your integration relies on a pre-agreed arbitrator outside of this list, you must still provide their name, email, and walletAddress in arbitrationSettings; however, prefer curated IDs whenever possible to avoid invitation rework.

Deadline handling

  • Deadlines are required and must be provided by the client; the backend performs strict validation.
  • Inputs must be UNIX timestamps expressed in seconds (milliseconds are normalized by dividing by 1000).
  • Validation rules:
  • acceptanceDeadline must be greater than the current block timestamp.
  • completionDeadline must be strictly greater than acceptanceDeadline.
  • completionDeadline cannot exceed the current timestamp by more than ~100 years.

On-chain execution

Funding, validation, arbitration, and payouts are executed directly on the DShare smart contract. PAYCIFI does not proxy or abstract these on-chain transactions for externally owned wallets; integrators perform blockchain interactions with their own tooling once the agreement has been created and mirrored on-chain.

Example Request

POST /agreements
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "participants": [
    {
      "id": "payer-temp",
      "userId": "3f6f6b4f-9c7d-4b9b-9f5a-43b0d4c2f101",
      "participantType": "payer",
      "name": "Acme Corp",
      "email": "payer@acme.com",
      "owner": true,
      "additionalInfo": "Main funding entity"
    },
    {
      "id": "provider-temp",
      "userId": "92a4b2b6-45f5-4501-8e71-0d371cbb9011",
      "participantType": "provider",
      "name": "Studio Nova",
      "email": "studio@nova.io",
      "additionalInfo": "Creative agency"
    }
  ],
  "agreementInfo": {
    "title": "Website Localization Sprint",
    "description": "Localize the main marketing site in FR/DE",
    "currency": "USDC",
    "totalAmount": 1800,
    "feePayerStrategy": "payer",
    "acceptanceDeadline": 1735603200,
    "completionDeadline": 1738281600,
    "ownerUserId": "3f6f6b4f-9c7d-4b9b-9f5a-43b0d4c2f101",
    "network": "base"
  },
  "agreementItems": [
    {
      "id": "item-1",
      "participantId": "provider-temp",
      "description": "Landing page localization",
      "quantity": 1,
      "unitPrice": 900,
      "fees": 45
    },
    {
      "id": "item-2",
      "participantId": "provider-temp",
      "description": "Product tour localization",
      "quantity": 1,
      "unitPrice": 900,
      "fees": 45
    }
  ],
  "arbitrationSettings": {
    "name": "DShare Panel",
    "email": "arbitration@paycifi.com",
    "phone": "+33180000000",
    "additionalInfo": "Escrow arbitration panel",
    "walletAddress": "0x0000000000000000000000000000000000000000"
  }
}

Example Response

{
  "success": true,
  "agreementId": "a55f1c3c-7de1-4ed3-8c90-9cf52a4b1d1a",
  "agreementInfo": {
    "id": "a55f1c3c-7de1-4ed3-8c90-9cf52a4b1d1a",
    "title": "Website Localization Sprint",
    "status": "pending",
    "currency": "USDC",
    "acceptanceDeadline": "2024-12-31T00:00:00.000Z",
    "completionDeadline": "2025-01-31T00:00:00.000Z",
    "payerTotalAmountWithFees": 1889.1
  },
  "participants": [
    {
      "id": "e0d1b4c5-0b80-4c08-8a58-90a8e4481f10",
      "participantType": "payer",
      "owner": true,
      "agreementStatus": "pending"
    },
    {
      "id": "17ce72bf-9b1a-4b70-8618-2afcce9f5f80",
      "participantType": "provider",
      "agreementStatus": "pending"
    },
    {
      "id": "db5a9fef-29ce-4cde-bc41-1881c5f5b24c",
      "participantType": "arbitrator",
      "agreementStatus": "pending"
    }
  ],
  "agreementItems": [
    {
      "id": "4e6f4d8f-5e6c-45bf-b4da-4208b0214a05",
      "participantId": "17ce72bf-9b1a-4b70-8618-2afcce9f5f80",
      "description": "Landing page localization",
      "providerItemStatus": "pending",
      "payerItemStatus": "pending"
    }
  ],
  "blockchainAgreement": {
    "agreementId": "0x613535f163332d6465312d34...",
    "contractAddress": "0xabc123...",
    "tokenAddress": "0xdef456...",
    "rpcUrl": "https://base-mainnet.g.alchemy.com/v2/...",
    "acceptanceDeadline": 1735603200,
    "completionDeadline": 1738281600
  }
}

Response Handling

  • Success: Persist agreementId, participants, and blockchainAgreement. Use participant IDs to drive acceptance (PATCH /agreements/participants/:participantId/status) and funding flows (/agreements/circle/fund/init).
  • Validation errors (400): Inspect errorCode/missingData to fix payload issues (e.g., missing-data, missing-acceptance-deadline, bad-completion-deadline). Client SHOULD block the user from proceeding until the payload is corrected.
  • Role or currency errors (400): agreementInfo.currency - unknown currency indicates the code is not supported. deadline-too-far indicates completion is beyond the allowed horizon.
  • Server errors (500): fees-calculation-error, agreement-creation-error, agreement-creation-service-error, or agreement-creation-controller-error require retry after verifying payload and backend health. If fromError is returned (non-production), log it for troubleshooting.
  • Funding errors (delegated path): forbidden-wallet-type, delegate-wallet-not-found, missing-required-fields, or invalid-bytes32-args come directly from the Circle funding endpoints when prerequisites are missing. Surface these to the payer and block funding until their session uses a delegate wallet and provides the required IDs.

Error Codes

  • missing-data
  • fees-calculation-error
  • missing-currency
  • missing-acceptance-deadline
  • missing-completion-deadline
  • bad-acceptance-deadline
  • bad-completion-deadline
  • deadline-too-far
  • agreement-creation-error
  • agreement-creation-service-error
  • agreement-creation-controller-error
  • POST /agreements – create the agreement and deploy it on-chain.
  • PATCH /agreements/participants/:participantId/status – collect accept/decline responses from each participant.
  • POST /agreements/:agreementId/recalculate-status – recompute the overall state after participant updates (optional helper).