Skip to content

Touli API and Event Contract Blueprint

Purpose

Define request/response contracts and internal event contracts for the deferred architecture modules.

Contract Versioning Strategy

  • Public API versioning:
    • URI version prefix: /v1/...
    • additive changes are backward-compatible
    • breaking changes require /v2
  • Event schema versioning:
    • event_name
    • event_version
    • schema_hash
    • consumers reject unknown required fields and tolerate unknown optional fields

Cross-Cutting Headers

Required on mutating APIs:

  • X-Request-Id
  • X-Idempotency-Key
  • X-Client-Version

API Contracts

1) Submit Deed

POST /v1/deeds

Request:

json
{
  "pillar": "social",
  "hoursClaimed": 3,
  "activityAt": "2026-06-22T08:00:00Z",
  "beneficiaryFlag": "others",
  "challengeId": "optional-uuid",
  "challengeTaskId": "optional-uuid",
  "location": { "lat": 6.9271, "lng": 79.8612 },
  "evidence": [
    { "type": "photo", "uploadRef": "storage/key/1" },
    { "type": "ngo_qr", "value": "QR_PAYLOAD" }
  ]
}

Response 202 Accepted:

json
{
  "submissionId": "uuid",
  "status": "pending",
  "statusUrl": "/v1/deeds/uuid/status",
  "receivedAt": "2026-06-22T08:00:02Z"
}

2) Get Verification Status

GET /v1/deeds/{submissionId}/status

Response:

json
{
  "submissionId": "uuid",
  "status": "processing",
  "verificationOutcome": null,
  "confidenceScore": null,
  "reasonCodes": [],
  "updatedAt": "2026-06-22T08:01:00Z"
}

Terminal response sample:

json
{
  "submissionId": "uuid",
  "status": "approved",
  "verificationOutcome": "approved",
  "verifyLevel": "peer",
  "groupSizeBucket": "group",
  "beneficiaryFlag": "others",
  "reasonCodes": ["PEER_CONFIRMED", "GPS_MATCHED"],
  "updatedAt": "2026-06-22T08:01:20Z"
}

3) Get Points Ledger

GET /v1/points/ledger?cursor=...&limit=...

Response:

json
{
  "items": [
    {
      "ledgerId": "uuid",
      "entryType": "deed_reward",
      "pointsDelta": 273,
      "createdAt": "2026-06-22T08:02:00Z",
      "configSnapshotId": "uuid",
      "reasonCodes": ["FORMULA_V4_2"]
    }
  ],
  "nextCursor": "opaque-cursor"
}

4) Get Anchor Proof (Internal/Operator)

GET /v1/points/ledger/{ledgerId}/anchor-proof

Response:

json
{
  "ledgerId": "uuid",
  "leafHash": "hex",
  "batchId": "uuid",
  "merkleRoot": "hex",
  "txHash": "0x...",
  "chainId": 137,
  "proofPath": ["hex1", "hex2"],
  "status": "confirmed"
}

Internal Event Contracts

Envelope

json
{
  "eventName": "VerificationCompleted",
  "eventVersion": 1,
  "eventId": "uuid",
  "occurredAt": "2026-06-22T08:01:20Z",
  "traceId": "uuid",
  "idempotencyKey": "string",
  "payload": {}
}

1) DeedSubmitted.v1

Payload:

json
{
  "submissionId": "uuid",
  "userId": "uuid",
  "pillar": "social",
  "hoursClaimed": 3,
  "beneficiaryFlag": "others",
  "activityAt": "2026-06-22T08:00:00Z"
}

Producer:

  • Submission service

Consumers:

  • Verification worker

2) VerificationCompleted.v1

Payload:

json
{
  "submissionId": "uuid",
  "userId": "uuid",
  "verificationOutcome": "approved",
  "verifyLevel": "peer",
  "groupSizeBucket": "group",
  "beneficiaryFlag": "others",
  "confidenceScore": 0.83,
  "reasonCodes": ["GPS_MATCHED", "PEER_CONFIRMED"],
  "decisionVersion": 1
}

Producer:

  • Verification worker

Consumers:

  • Points worker, notifications

3) PointsCredited.v1

Payload:

json
{
  "ledgerId": "uuid",
  "submissionId": "uuid",
  "userId": "uuid",
  "entryType": "deed_reward",
  "pointsDelta": 273,
  "configSnapshotId": "uuid"
}

Producer:

  • Points worker

Consumers:

  • Anchor worker, read model projector, notifications

4) AnchorBatchCreated.v1

Payload:

json
{
  "batchId": "uuid",
  "leafCount": 231,
  "merkleRoot": "hex",
  "windowStart": "2026-06-22T00:00:00Z",
  "windowEnd": "2026-06-22T23:59:59Z"
}

Producer:

  • Anchor worker

Consumers:

  • Ops monitoring

5) AnchorConfirmed.v1

Payload:

json
{
  "batchId": "uuid",
  "txHash": "0x...",
  "blockNumber": 123456,
  "confirmationCount": 20,
  "confirmedAt": "2026-06-23T00:07:00Z"
}

Producer:

  • Anchor worker

Consumers:

  • Audit surfaces, reporting

Idempotency Rules

  • API mutating operations require X-Idempotency-Key.
  • Event processing key:
    • eventName + eventVersion + payload.primaryId + decisionVersion
  • Consumers must store processed key and no-op duplicates.

Compatibility Rules

  • Additive optional fields are allowed in same version.
  • New required field requires event version bump.
  • Deprecated fields retained for at least one full release cycle.