LayerCover

Headless API Reference

REST API endpoints for 3rd party integrations without UI components

Build custom integrations using our REST API. Users sign transactions with their own wallet (MetaMask, etc.).


Integration Flow

1. GET /api/pools/list           → List available pools
2. GET /api/quote                → Get pricing
3. POST /api/purchase/prepare    → Get transaction calldata
4. User signs EIP-712 payload(s) → Submit to blockchain
5. POST /api/purchase/sync       → Verify tx + update quote capacity

GET /api/quote

Returns pricing for a coverage quote.

Request

GET /api/quote?poolId=1&amount=100000000&duration=12&deployment=base_sepolia_usdc
ParameterTypeRequiredDescription
poolIdnumberYesPool ID to quote
amountstringYesCoverage amount in smallest unit (wei)
durationnumberYesDuration in weeks
deploymentstringNoDeployment name (default: base_sepolia_usdc)

Response

{
  "success": true,
  "quote": {
    "poolId": 1,
    "coverageAmount": "100000000",
    "durationWeeks": 12,
    "durationSeconds": 7257600,
    "premiumRateBps": 100,
    "premiumRatePercent": 1,
    "premiumAmount": "229979",
    "availableCapacity": "650697000000",
    "quotesUsed": [
      {
        "quoteId": "0x52f1985F...1767528715765",
        "syndicateName": "ClearStar",
        "amount": "100000000",
        "rateBps": 100
      }
    ]
  }
}

Example

curl "https://app.layercover.com/api/quote?poolId=1&amount=100000000&duration=12"

POST /api/purchase/prepare

Returns unsigned transaction calldata for wallet submission.

Request

POST /api/purchase/prepare
Content-Type: application/json

{
  "quoteId": "0x52f1985F...1767528715765",
  "amount": "100000000",
  "durationWeeks": 12,
  "buyerAddress": "0xYourWalletAddress",
  "referralCode": "0x1234..."  // optional
}
FieldTypeRequiredDescription
quoteIdstringYesQuote ID from /api/quote response
amountstringYesCoverage amount in smallest unit
durationWeeksnumberYesDuration in weeks
buyerAddressstringYesUser's wallet address
referralCodestringNoYour referral code (bytes32)

Response

{
  "success": true,
  "transactions": {
    "approval": {
      "to": "0xTokenAddress...",
      "data": "0x095ea7b3...",
      "description": "Approve USDC spending"
    },
    "executePurchase": {
      "to": "0xIntentMatcher...",
      "data": "0xd2d599e7...",
      "description": "Execute matched intent purchase (replace placeholder signatures before broadcast)",
      "requiresOrderSignature": true,
      "requiresPermit2Signature": true
    }
  },
  "signing": {
    "order": {
      "domain": { "name": "IntentMatcher", "version": "1", "chainId": 84532, "verifyingContract": "0xIntentMatcher..." },
      "types": { "CoverageBuyOrder": [/* ... */] },
      "message": { /* ... */ }
    },
    "permit2": {
      "domain": { "name": "Permit2", "chainId": 84532, "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3" },
      "types": { "PermitTransferFrom": [/* ... */], "TokenPermissions": [/* ... */] },
      "primaryType": "PermitTransferFrom",
      "message": { /* ... */ }
    }
  },
  "quote": {
    "premiumAmount": "229979",
    "premiumDeposit": "229979"
  },
  "chainId": 84532
}

Submitting Transactions

Use the returned payload with ethers, wagmi, or raw eth_sendTransaction:

// 1. Approval transaction
await signer.sendTransaction({
  to: response.transactions.approval.to,
  data: response.transactions.approval.data
});

// 2. Sign EIP-712 order payload
const orderSignature = await signer.signTypedData(
  response.signing.order.domain,
  response.signing.order.types,
  response.signing.order.message
);

// 3. (Optional) Sign Permit2 payload when required
const permit2Signature = response.signing.permit2
  ? await signer.signTypedData(
      response.signing.permit2.domain,
      response.signing.permit2.types,
      response.signing.permit2.message
    )
  : '0x';

// 4. Replace placeholder signatures in executePurchase calldata
const executeData = replaceExecuteMatchedIntentSignatures(
  response.transactions.executePurchase.data,
  { orderSignature, permit2Signature }
);

// 5. Broadcast executeMatchedIntent transaction
await signer.sendTransaction({
  to: response.transactions.executePurchase.to,
  data: executeData
});

POST /api/purchase/sync

Verifies the transaction on-chain, resolves the matching IntentMatched event, then updates quote capacity.

Request

POST /api/purchase/sync
Content-Type: application/json

{
  "quoteId": "0x52f1985F...1767528715765",
  "txHash": "0xabc123...",
  "filledAmount": "100000000",
  "idempotencyKey": "purchase-sync:84532:0xabc123...:0x52f1985F...1767528715765"
}

Response

{
  "success": true,
  "quoteId": "0x52f1985F...1767528715765",
  "txHash": "0xabc123...",
  "matchedEvent": {
    "policyId": "42",
    "coverageAmount": "100000000",
    "logIndex": 7
  },
  "idempotencyKey": "purchase-sync:84532:0xabc123...:0x52f1985F...1767528715765",
  "matchId": "0xabc123...:7"
}

Use this endpoint as the primary client integration path.


GET /api/pools/list

Returns all available pools with metadata.

Request

GET /api/pools/list?deployment=base_sepolia_usdc

Response

{
  "success": true,
  "pools": [
    {
      "id": 1,
      "name": "Aave USDC",
      "tokenSymbol": "USDC",
      "totalCoverageSold": "50000000000",
      "availableCapacity": "650000000000",
      "lowestQuoteRateBps": 100
    }
  ]
}

Error Responses

All error responses include a machine-readable code field for programmatic handling:

{
  "error": "Human-readable error message",
  "code": "MISSING_FIELDS"
}

Some errors include additional context:

{
  "error": "Quote exceeds available syndicate capacity",
  "code": "CAPACITY_EXCEEDED",
  "currentExposure": "50000000000",
  "availableCapacity": "10000000000"
}

Error Codes

CodeHTTPDescription
MISSING_PARAMS400Missing required query parameters (poolId or syndicateAddress)
MISSING_FIELDS400Missing required fields in request body
MISSING_QUOTE_ID400quoteId parameter is required
MISSING_POOL_IDS400poolIds parameter is required (batch endpoint)
MISSING_UPDATES400updates array is required (fill endpoint)
MISSING_DEPLOYMENT400deployment is required (clear endpoint)
MISSING_SYNDICATE_ADDRESS400syndicateAddress is required (exposure endpoint)
MISSING_CAPACITY400currentCapacity is required
INVALID_POOL_IDS400No valid pool IDs provided
INVALID_DURATION400Invalid duration in coverageIntent
INVALID_UPDATE400Invalid fill update structure
INVALID_CAPACITY400Invalid currentCapacity value
QUOTE_EXPIRED400Quote expiry must be in the future
QUOTE_CONSUMED400Quote has been fully consumed
QUOTE_NOT_FOUND404Quote with given ID not found
CAPACITY_EXCEEDED400Quote exceeds syndicate's on-chain capacity
NO_UPDATES400No quote updates provided
INTERNAL_ERROR500Server error

Use code for programmatic branching (e.g., retry on INTERNAL_ERROR, show capacity UI on CAPACITY_EXCEEDED). Use error for user-facing messages.


Referral Codes

Pass your referral code to /api/purchase/prepare to attribute the purchase. The default suggested reward is often 5%, but the live rate is configurable per deployment and capped at 10%:

{
  "quoteId": "...",
  "amount": "100000000",
  "durationWeeks": 12,
  "buyerAddress": "0x...",
  "referralCode": "0x1234567890abcdef..."
}

Register for a referral code at app.layercover.com. Reward funding and claiming are handled by the on-chain referral ledger.


Quote Submission (For Underwriters)

Syndicates can programmatically submit coverage quotes to the orderbook.

POST /api/quotes

Submit a new coverage quote.

Request

POST /api/quotes
Content-Type: application/json

{
  "poolId": 1,
  "deployment": "base_sepolia_usdc",
  "syndicateAddress": "0xYourSyndicateVault...",
  "coverageAmount": "10000000000",
  "premiumRateBps": 500,
  "minDurationWeeks": 4,
  "maxDurationWeeks": 12,
  "reserveIntent": {
    "solver": "0xYourAddress...",
    "underwriter": "0xYourSyndicateVault...",
    "poolId": 1,
    "minCoverageDuration": 2419200,
    "maxCoverageDuration": 7257600,
    "coverageAmount": "10000000000",
    "minFillAmount": "10000000000",
    "allowPartialFill": false,
    "reservationExpiry": 1736265600,
    "nonce": "1736179200000"
  },
  "signature": "0xReserveIntentSignature...",
  "coverageIntent": {
    "maker": "0xYourSyndicateVault...",
    "poolId": 1,
    "coverageAmount": "10000000000",
    "premiumRateBps": 500,
    "minDuration": 2419200,
    "maxDuration": 7257600,
    "nonce": "1736179200000",
    "expiry": 1736265600,
    "salt": "0x..."
  },
  "intentSignature": "0xCoverageIntentSignature..."
}

Response

{
  "success": true,
  "quote": {
    "id": "0xSyndicate...-1736179200000-0x...",
    "poolId": 1,
    "syndicateAddress": "0x...",
    "coverageAmount": "10000000000",
    "premiumRateBps": 500,
    "status": "active"
  }
}

GET /api/quotes/exposure

Get total quoted exposure for a syndicate.

GET /api/quotes/exposure?syndicateAddress=0x...

Response

{
  "success": true,
  "syndicateAddress": "0x...",
  "totalExposure": "50000000000",
  "activeQuoteCount": 5
}

DELETE /api/quotes

Cancel an existing quote.

DELETE /api/quotes?quoteId=0xSyndicate...-1736179200000-0x...

SDK Usage

import { LayerCoverSDK } from '@layercover/sdk';

const sdk = new LayerCoverSDK(signer, policyManagerAddress, {
  apiBaseUrl: 'https://app.layercover.com'
});

// Submit a quote
const result = await sdk.submitQuote({
  poolId: 1,
  syndicateAddress: '0x...',
  coverageAmount: ethers.parseUnits('10000', 6),
  premiumRateBps: 500, // 5% APY
  minDurationWeeks: 4,
  maxDurationWeeks: 12,
});

console.log('Quote submitted:', result.quoteId);

// Get exposure
const exposure = await sdk.getSyndicateExposure('0x...');
console.log('Total exposure:', exposure.totalExposure);

// Cancel quote
await sdk.cancelQuote(result.quoteId);

EIP-712 Signing Requirements

Quote submission requires two signatures:

  1. ReserveIntent (signed against Syndicate contract)

    • Domain: { name: 'Syndicate', version: '1', chainId, verifyingContract: syndicateAddress }
  2. CoverageIntent (signed against IntentMatcher contract)

    • Domain: { name: 'IntentMatcher', version: '1', chainId, verifyingContract: intentMatcherAddress }

See the SDK source for complete type definitions.


Full Example

const API = 'https://app.layercover.com';

// 1. Get quote
const quoteRes = await fetch(`${API}/api/quote?poolId=1&amount=100000000&duration=12`);
const { quote } = await quoteRes.json();

// 2. Prepare purchase
const prepRes = await fetch(`${API}/api/purchase/prepare`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    quoteId: quote.quotesUsed[0].quoteId,
    amount: '100000000',
    durationWeeks: 12,
    buyerAddress: userAddress,
    referralCode: 'YOUR_REFERRAL_CODE'
  })
});
const { transactions, signing } = await prepRes.json();

// 3. Submit via user's wallet
const approvalTx = await signer.sendTransaction(transactions.approval);
await approvalTx.wait();

const orderSignature = await signer.signTypedData(
  signing.order.domain,
  signing.order.types,
  signing.order.message
);
const permit2Signature = signing.permit2
  ? await signer.signTypedData(signing.permit2.domain, signing.permit2.types, signing.permit2.message)
  : '0x';

const executeData = replaceExecuteMatchedIntentSignatures(
  transactions.executePurchase.data,
  { orderSignature, permit2Signature }
);

const purchaseTx = await signer.sendTransaction({
  to: transactions.executePurchase.to,
  data: executeData
});
await purchaseTx.wait();

// 4. Sync (tx-verified)
await fetch(`${API}/api/purchase/sync`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    quoteId: quote.quotesUsed[0].quoteId,
    txHash: purchaseTx.hash,
    filledAmount: '100000000',
    idempotencyKey: `purchase-sync:84532:${purchaseTx.hash.toLowerCase()}:${quote.quotesUsed[0].quoteId}`
  })
});

Need Help?