# AfCFTA E-CO — full English documentation corpus > Assembled from apps/docs/content/en. For a curated map, read /llms.txt first. --- ## Platform overview Path: / The **AfCFTA E-CO platform** is the continental **exchange hub** for Electronic Certificates of Origin. National and REC systems **issue, process, and approve**. This hub **only exchanges**: it receives pushed certificate data, integrity-signs and stores permanently, routes (direct or via REC), and answers verification. That boundary matches the AfCFTA BPA and clarifications: few central users; the platform moves trusted data, it does not replace national issuance. ## What evaluators and partners get | Surface | What it delivers | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | **REST exchange API** | Issued certificates, amendments, treatment status, verification, query/pull, webhooks, sandbox and live API keys | | **Secretariat console** | Monitoring, exchange log, State Party onboarding, registry analytics, contact points (BR.3), RECs, audit | | **Public / staff verify** | Real-time authenticity check by certificate number or QR, no citizen login required for outside users | | **Reference national connector** | End-to-end demo of national issue → Approve & push → hub exchange (issuance stays national) | | **Docs + OpenAPI + SDK** | Six AU languages for functional docs; English API/admin docs; Node SDK and Postman collection | ## Aligned to the Secretariat design The running build demonstrates the **central hub happy path**: aligned to the AfCFTA Business Process Analysis (BPA) and continental dataset: 1. A national system issues and signs an E-CO, then **pushes** certificate data to the hub 2. The hub stores permanently, routes to the destination State Party (or via REC when configured), and records the exchange 3. Verifiers confirm authenticity in real time (API and staff verify surfaces) 4. Secretariat operators monitor traffic, onboard pilots, and manage keys from the console ## How the platform grows The platform starts with a clear job: help countries exchange and verify electronic certificates of origin. First we connect the pilot State Parties. Then we bring more countries and regional systems onto the same path. Over time the continental record of certificates supports monitoring and better trade decisions. We stay focused on this: countries issue certificates; the hub moves them, checks them, and keeps the shared record. ## Quick links | Resource | Location | | ---------------------------- | ---------------------------------------------------------- | | Web application (hub) | https://afcfta-eco.org | | Platform API | https://api.afcfta-eco.org | | API docs (Swagger UI) | https://api.afcfta-eco.org/api/docs | | Documentation (this site) | https://docs.afcfta-eco.org | | Reference national connector | https://national.afcfta-eco.org | | Public verification | `/verify` on the web app | | Exchange endpoints | `POST /api/exchange/push`, `GET /api/exchange/out/:number` | | Pilot integration kit | [Pilot integration kit](/guides/pilot-integration-kit) | ## Who should read what | Audience | Start here | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | TWG / bid reviewers | [Getting started](/guides/getting-started) → two-minute walkthrough, then [Integration model](/architecture/integration-model) | | Integration engineers | [National systems overview](/guides/national-systems/overview) | | Customs IT teams | [Authentication](/guides/national-systems/authentication) → [Exchange in](/guides/national-systems/exchange-in) | | Secretariat staff | [Hybrid system](/architecture/hybrid-system) → [Secretariat manual](/guides/user-manuals/secretariat) | ## Platform boundaries > Working continental hub, ready for Secretariat-hosted Dev / Test / Prod / DR. Production PKI/HSM keys, notification transport, and pilot UAT run on Secretariat infrastructure per the TOR. - **Hub, not issuer:** partners connect via a standard REST API; certificates are issued in national systems, never on the hub - **RECs use the same contract** as national systems, routing supports `direct` and `via_rec` - **Cryptography:** Ed25519 in the current demo; production switches to PKI / X.509 / HSM through the signing provider abstraction - **Notifications:** email via SMTP or Resend, phone via Twilio SMS or Kapso WhatsApp — see [Notifications](/architecture/notifications) - **Test mode is isolated**: sandbox keys and certificates do not pollute live registry statistics - **Languages:** UI and functional documentation in all six AU languages; technical/API documentation in English ## Monorepo layout ``` eco/ ├── apps/ │ ├── api/ # NestJS 11 REST API (exchange, verify, registry, admin, Better Auth) │ ├── web/ # Next.js 16 hub UI (verify, registry, Secretariat admin) │ ├── docs/ # Next.js 16 documentation site (6 AU languages) │ └── national-ref/ # Reference national connector (registration → issuance → push) ├── packages/ │ ├── shared/ # Zod schemas, roles, OpenAPI, Postman, public URL helpers │ ├── db/ # Drizzle schema + migrations │ ├── auth/ # Better Auth server + React client │ ├── sdk-node/ # Official Node.js client for the hub REST API │ ├── ui/ # Shared Blueprint React components, tokens, RTL │ └── tsconfig/ # Shared TypeScript configs ├── infra/ │ ├── deploy/ # Docker Compose, Secretariat runbooks, UAT templates │ └── load-tests/ # k6 scripts (verify, exchange, REC routing) └── docs/build/ # Secretariat annexes and architecture extracts ``` ## Next steps 1. Run the [two-minute demo walkthrough](/guides/getting-started#two-minute-demo-walkthrough) 2. Review the [integration model](/architecture/integration-model) before building a connector 3. Use the [exchange API reference](/api-reference/exchange) and [OpenAPI](/api-reference/openapi) for payloads --- ## API reference Path: /api-reference Base URL: `https://api.afcfta-eco.org` > Interactive Swagger UI is served at `/api/docs`, and the raw contract at `/api/docs-json`. The committed spec lives at `packages/shared/openapi/openapi.json` (see [OpenAPI specification](/api-reference/openapi)). ## Authentication summary | Client | Method | Header / cookie | | --------------- | --------- | ---------------------------------------------------------- | | Human users | Session | Better Auth cookie | | Machine clients | API key | `x-api-key: eco_{test\|live}_{hex}` | | Machine clients | OAuth 2.0 | `Authorization: Bearer ` from `POST /api/oauth/token` | | Public | None | Verification, health | ## Endpoints | Method | Path | Auth | Description | | --------------------- | ------------------------------------- | --------------------------- | -------------------------------------- | | GET | `/api/health` | Public | Liveness + DB connectivity | | POST | `/api/auth/*` | Public | Better Auth routes | | GET | `/api/verify/:number` | Public | Verify authenticity | | GET | `/api/verify/public-key` | Public | Hub signing public key (DER, base64) | | POST | `/api/exchange/push` | API key | Push message to hub (canonical) | | POST | `/api/exchange/in` | API key | Deprecated alias of `push` | | GET | `/api/exchange/out/:number` | API key | Pull cert from central node | | GET | `/api/registry/*` | Verifier+ | Stats, trends, export, queries | | GET/POST/PATCH | `/api/state-parties` | Admin | State party CRUD | | POST | `/api/state-parties/onboard` | Admin | Pilot onboarding | | POST | `/api/state-parties/:id/promote-live` | Admin | Promote pilot to live | | GET/POST/PATCH | `/api/issuing-authorities` | Admin | Issuing authority CRUD | | POST/GET/DELETE | `/api/api-keys` | Secretariat admin | API key management | | POST/GET/DELETE | `/api/oauth-clients` | Secretariat admin | OAuth client management | | POST | `/api/oauth/token` | Public (client credentials) | OAuth 2.0 access token | | GET | `/api/audit` | Admin | Queryable audit trail | | GET/POST | `/api/recs` | Public / Admin | List / manage REC federation | | POST/GET/DELETE/PATCH | `/api/webhooks/*` | Admin | Webhook registration + delivery log | | GET | `/api/monitoring/*` | Admin | Overview + exchange message log | | GET/POST/PATCH/DELETE | `/api/contact-points` | Admin | Technical/business/escalation contacts | | POST | `/api/demo/login` | Demo mode | One-click demo persona login | > **Certificates are issued in national systems, not on the hub.** State Party systems create certificates and send them to the hub via `POST /api/exchange/push`. That is why there is no `/api/certificates` endpoint here. ## Detailed references - [Verification](/api-reference/verification): public authenticity check - [Exchange](/api-reference/exchange): push/pull for national systems - [OAuth](/api-reference/oauth): client credentials machine auth - [Registry](/api-reference/registry): stats and directory queries - [Monitoring](/api-reference/monitoring): operational overview - [Admin](/api-reference/admin): keys, clients, parties, webhooks - [OpenAPI](/api-reference/openapi): machine-readable contract ## Roles | Role | Access | | ------------------- | ------------------------------------- | | `secretariat-admin` | Full admin, API keys, monitoring | | `authority-admin` | Authority user management | | `issuer` | Issue certificates in national system | | `verifier` | Registry stats, search | | `exporter` | Register and apply in national system | ## Shared schemas Request and response shapes are defined in `packages/shared/src/schemas.ts` and validated with Zod on both API and client. --- ## Admin API Path: /api-reference/admin | Method | Path | Description | | ------ | -------------------------- | ------------------------ | | GET | `/api/api-keys` | List API keys | | GET | `/api/oauth-clients` | List OAuth clients | | GET | `/api/state-parties` | List state parties | | GET | `/api/issuing-authorities` | List issuing authorities | | GET | `/api/webhooks` | List webhooks | | GET | `/api/contact-points` | List contact points | | GET | `/api/audit` | Query audit trail | --- ## API errors Path: /api-reference/errors Partner-facing responses use a consistent JSON shape. When the hub can classify the failure, it also returns a stable `code` field (see `@eco/shared` `ECO_API_ERRORS`). ## Response shape ```json { "statusCode": 401, "code": "API_KEY_REQUIRED", "message": "API key or OAuth bearer token required", "timestamp": "2026-07-01T12:00:00.000Z" } ``` Validation failures may include an `errors` array with field details. ## Error catalog | Code | HTTP | Message | What to do | | ------------------------- | ---- | ------------------------------------------ | ---------------------------------------- | | `API_KEY_REQUIRED` | 401 | API key or OAuth bearer token required | Send `x-api-key` or OAuth Bearer | | `API_KEY_INVALID` | 401 | Invalid or revoked API key | Rotate or request a new key | | `AUTH_REQUIRED` | 401 | Authentication required | Sign in for session-only routes | | `FORBIDDEN` | 403 | Insufficient permissions | Wrong role for the route | | `VALIDATION_FAILED` | 400 | Validation failed | Fix envelope / payload vs OpenAPI | | `CERTIFICATE_EXISTS` | 400 | Certificate already exists in registry | Treat as registered; use Idempotency-Key | | `CERTIFICATE_NOT_FOUND` | 404 | Certificate not found | Check number and destination key | | `IDEMPOTENCY_CONFLICT` | 409 | Idempotency key already in use | Same key only with the same body | | `OAUTH_UNSUPPORTED_GRANT` | 400 | Only client_credentials grant is supported | Use `client_credentials` | | `OAUTH_INVALID_CLIENT` | 401 | Invalid client credentials | Check client id/secret | | `INTERNAL_ERROR` | 500 | Internal server error | Retry with backoff | Narrative national-system guidance: [Error handling](/guides/national-systems/error-handling). ## Retries and idempotency 1. Generate a stable `Idempotency-Key` per logical push and reuse it on retries. 2. On `5xx` / network errors, retry with exponential backoff and the same key. 3. On `CERTIFICATE_EXISTS`, treat the certificate as already registered. 4. On `IDEMPOTENCY_CONFLICT`, wait briefly or inspect logs — do not invent a new key for the same body. ## Related - [Exchange API](/api-reference/exchange) — Try panel for push / pull - [OpenAPI specification](/api-reference/openapi) — interactive explorer + download links - [Node SDK](/guides/sdk) --- ## Exchange API Path: /api-reference/exchange Machine-to-machine endpoints for national and REC systems. ## POST /api/exchange/push Push a message into the central registry. Every push uses the same envelope; the `messageType` selects the `payload` shape. Plain names and RFP shorthand: [Message types](/guides/national-systems/message-types). **Auth:** `x-api-key: eco_{test|live}_{hex}` **Content-Type:** `application/json` Optional header: `Idempotency-Key` for safe retries. **Envelope:** ```json { "messageType": "T1_CO_DATA", "countryOrigin": "GH", "countryDestination": "KE", "payload": {} } ``` **Supported `messageType` values:** | messageType | Payload cluster | Purpose | | -------------------------- | ------------------- | --------------------------------------- | | `T1_CO_DATA` | ECO Main + products | Push issued certificate data to the hub | | `T2_AMENDMENT` | Amendment request | Request revoke / cancel | | `T2_AMENDMENT_RESPONSE` | Amendment response | Accept / decline an amendment | | `T3_TREATMENT_STATUS` | Usage | Report import preferential treatment | | `T4_VERIFICATION_REQUEST` | Verification | Request verification of a certificate | | `T4_VERIFICATION_RESULT` | Verification | Return a verification result | | `QUERY` / `QUERY_RESPONSE` | Query | Free-text query and response | See [Exchange in guide](/guides/national-systems/exchange-in) for the full payload schema and examples. > `POST /api/exchange/in` is a **deprecated alias** of `push` and will be removed in a future release. ### Success response For `T1_CO_DATA`, returns the stored certificate (same shape as the pull response below) with a generated signature if one was not supplied. For other message types, returns the created amendment / usage / verification / query record and the affected `certificateNumber`. ### Error responses | Status | Cause | | ------ | ----------------------------------------------------------------------- | | 400 | Validation failure (Zod schema mismatch) | | 401 | Missing or invalid API key | | 400 | Duplicate certificate number (`Certificate already exists in registry`) | --- ## GET /api/exchange/out/:number Pull a certificate by number from the central registry. **Auth:** `x-api-key: eco_{test|live}_{hex}` **Path parameter:** `number`, certificate number (e.g. `202607GHMUB8F0B9`) See [Exchange out guide](/guides/national-systems/exchange-out) for usage examples. ### Success response Full certificate payload matching the exchange push schema, plus status and timestamps. ### Error responses | Status | Cause | | ------ | -------------------------- | | 404 | Certificate not found | | 401 | Missing or invalid API key | ## Implementation reference Source: `apps/api/src/exchange/exchange.service.ts` Payload schema: `packages/shared/src/schemas.ts` → `exchangePushSchema` (a Zod discriminated union on `messageType`) --- ## Monitoring API Path: /api-reference/monitoring | Method | Path | Description | | ------ | ----------------------------------- | -------------------- | | GET | `/api/monitoring/overview` | Operational overview | | GET | `/api/monitoring/exchange-messages` | Exchange message log | --- ## OAuth 2.0 machine authentication Path: /api-reference/oauth National systems may authenticate with **API keys** (`x-api-key: eco_{test|live}_*`) or **OAuth 2.0 client credentials** (Bearer access token). Both paths share the same exchange scopes. ## Obtain an access token `POST /api/oauth/token` ```json { "grant_type": "client_credentials", "client_id": "eco_client_…", "client_secret": "eco_secret_…" } ``` Alternatively send `Authorization: Basic base64(client_id:client_secret)`. Response: ```json { "access_token": "", "token_type": "Bearer", "expires_in": 3600 } ``` ## Call exchange endpoints ```http POST /api/exchange/push Authorization: Bearer Content-Type: application/json ``` ## Secretariat administration Secretariat admins create and revoke OAuth clients via: - `POST /api/oauth-clients` - `GET /api/oauth-clients` - `DELETE /api/oauth-clients/:id` Clients are scoped to `test` or `live` mode and optionally bound to a State Party or REC, matching API key behaviour. ## SDK ```typescript import { createEcoClient } from '@eco/sdk-node'; const client = createEcoClient({ baseUrl: 'https://api.afcfta-eco.org', oauth: { clientId: process.env.ECO_CLIENT_ID!, clientSecret: process.env.ECO_CLIENT_SECRET!, }, }); await client.pushCoData(payload, 'GH', 'KE'); ``` --- ## OpenAPI specification Path: /api-reference/openapi The OpenAPI 3.x contract is **generated directly from the NestJS code**, so it tracks the live API. The interactive explorer on this page loads the docs-host copy of the spec. Live request panels also sit on each API reference page ([Verification](/api-reference/verification), [Exchange](/api-reference/exchange), [OAuth](/api-reference/oauth), [Registry](/api-reference/registry), [Monitoring](/api-reference/monitoring), [Admin](/api-reference/admin)). ## Where to find it | Artifact | Location | | -------------------------------- | ------------------------------------------------------------------------------------ | | Interactive explorer (this page) | `https://docs.afcfta-eco.org/en/api-reference/openapi` | | Docs OpenAPI JSON | `https://docs.afcfta-eco.org/openapi.json` | | Agent OpenAPI (curated subset) | `https://docs.afcfta-eco.org/agent-openapi.json` | | Agent card | `https://docs.afcfta-eco.org/.well-known/agent.json` | | Swagger UI (API host) | `https://api.afcfta-eco.org/api/docs` | | Live OpenAPI JSON | `https://api.afcfta-eco.org/api/docs-json` | | Committed spec | `packages/shared/openapi/openapi.json` | | Postman collection | regenerate with `pnpm postman:generate` (committed under `packages/shared/postman/`) | | Node SDK | [Node SDK guide](/guides/sdk) | | `llms.txt` | `https://docs.afcfta-eco.org/llms.txt` | ## Import and download - **OpenAPI JSON:** [openapi.json](/openapi.json) — import into Postman, Insomnia, or Scalar. - **Agent subset:** [agent-openapi.json](/agent-openapi.json) — health, verify, exchange only. - **Postman:** run `pnpm postman:generate` from the monorepo root and import `packages/shared/postman/eco-api.postman_collection.json`. ## Regenerating ```bash # From repo root, regenerate the committed spec and llms.txt from code pnpm openapi:generate pnpm llms:generate pnpm postman:generate ``` Drift checks (`openapi:check`, `postman:check`, `llms:check`) fail CI if committed artifacts are stale. ## Covered endpoints | Area | Representative paths | Auth | | ------------ | --------------------------------------------------------------------------------------------- | ------- | | Health | `GET /api/health` | Public | | Verification | `GET /api/verify/{certificateNumber}`, `GET /api/verify/public-key` | Public | | Exchange | `POST /api/exchange/push`, `GET /api/exchange/out/{certificateNumber}` | API key | | Registry | `GET /api/registry/{stats,trends,export,authorities,state-parties}` | Session | | Onboarding | `POST /api/state-parties/onboard`, `POST /api/state-parties/{id}/promote-live` | Session | | Admin | `state-parties`, `issuing-authorities`, `api-keys`, `recs`, `webhooks`, `audit`, `monitoring` | Session | > Certificates are issued in national systems, not on the hub. There is no `/api/certificates` endpoint — national systems send certificate data via `POST /api/exchange/push`. ## Schema alignment Request/response shapes align with the Zod schemas in `@eco/shared`. Partner error codes are documented in [API errors](/api-reference/errors) and appear as OpenAPI `ErrorResponse` examples. --- ## Registry API Path: /api-reference/registry Verifier+ session endpoints for continental registry reads. | Method | Path | Description | | ------ | ----------------------------- | ----------------------------- | | GET | `/api/registry/stats` | Aggregate registry statistics | | GET | `/api/registry/trends` | Time-series trends | | GET | `/api/registry/export` | Export registry rows | | GET | `/api/registry/authorities` | Issuing authorities directory | | GET | `/api/registry/state-parties` | State parties directory | --- ## Verification API Path: /api-reference/verification ## GET /api/verify/:number Check whether a certificate is authentic and retrieve its details. **Auth:** None (public endpoint) **Path parameter:** `certificateNumber` ### Example ```bash curl https://api.afcfta-eco.org/api/verify/202607GHMUB8F0B9 ``` ### Response fields The endpoint always returns `200`; the `authentic` / `status` fields describe the result (unknown numbers return `status: "not_found"` with `certificate: null`). | Field | Type | Description | | ---------------- | -------------- | ---------------------------------------------------------------------------------------- | | `authentic` | boolean | `true` when the certificate exists, is not revoked/cancelled, and the signature is valid | | `signatureValid` | boolean | Whether the Ed25519 signature verifies | | `status` | string | Certificate status, or `not_found` | | `certificate` | object \| null | Certificate details when found (see below) | **`certificate` object** (when found): `certificateNumber`, `applicationType`, `countryOrigin`, `countryDestination`, `mode`, `issueDate`, `approvalDate`, `signature`, `stateParty`, `issuingAuthority`, `exporterName`, `exporterAddress`, `consigneeName`, `consigneeAddress`, `products`. ### Status meanings | Status | Verification result | | ----------------------- | ----------------------------------- | | `ISSUED` | Valid, registered on the hub | | `EXCHANGED` | Valid, received via exchange push | | `AMENDED` | Valid, amended after issue | | `REVOKED` / `CANCELLED` | `authentic: false`, no longer valid | | `not_found` | Number not present in the registry | ## GET /api/verify/public-key Returns the hub's Ed25519 public key for offline signature verification. **Auth:** None (public) | Field | Description | | ----------- | ------------------------------- | | `algorithm` | Signature algorithm (`ed25519`) | | `format` | `der-spki-base64` | | `publicKey` | Base64-encoded public key | ## Web UI equivalent The public verification page at `/verify` on the web app calls this API internally. ## Related - [Verification guide](/guides/verification) - [API reference overview](/api-reference) --- ## Hybrid system architecture Path: /architecture/hybrid-system The AfCFTA E-CO platform is the **central node** in a hybrid continental architecture. It is not a national customs system and not a REC system, it is the **interconnection layer** that: - Signs (when a signature is not supplied) and registers exchanged certificate data, **certificates are still issued in national systems, never on the hub** - Verifies certificate authenticity in real time - Receives and routes certificates exchanged between State Parties and RECs - Maintains a continental registry for statistics and policy monitoring ## Hub-and-spoke model ```mermaid flowchart TB GH["National CoO
System (GH)"] KE["National CoO
System (KE)"] REC["REC (COMESA,
EAC, etc.)"] HUB["AfCFTA E-CO Central Node
Registry · Exchange · Verify"] PUB["Public
Verifier"] GH --> HUB KE --> HUB REC --> HUB PUB --> HUB ``` Partners connect **to the central node** via a standard REST exchange API. The hub does not need to know how each national or REC system works internally, it requires a **standardised certificate payload** and **trust via API key + signature**. **Next:** [Getting started](/guides/getting-started) · [Building a connector](/guides/national-systems/building-connector) ## Capabilities | Component | Status | | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | Public verification | Built | | Exchange API (push/pull for certificates, amendments, treatment status, verification, queries) | Built | | Continental registry + stats/trends/export | Built | | RBAC (5 roles) | Built | | API keys for machine clients | Built (test/live isolation) | | REC federation (`direct` / `via_rec`) | Built | | Secretariat & authority admin UI | Built (`apps/web`) | | Reference national system | Built (`apps/national-ref`) | | Webhooks + delivery log + retries | Built | | Idempotency (`Idempotency-Key`) | Built | | Audit trail + monitoring API | Built | | Rate limiting | Built (configurable throttle) | | OpenAPI + `llms.txt` generation | Built (generated from code) | | Certificate signing | Ed25519 default; X.509 / PKCS#11 (HSM) via `SIGNING_PROVIDER` | ### Production evolution The remaining production hardening focuses on HSM-backed key custody, a security assessment, key-rotation runbooks, and deployment across Secretariat Dev/Test/Prod/DR environments. ## Platform components | Layer | Location | Role | | ------------------------- | -------------------------------- | -------------------------- | | Business logic | `apps/api/src/` | NestJS modules | | Data model | `packages/db/src/schema/` | Drizzle + PostgreSQL | | Shared contracts | `packages/shared/src/schemas.ts` | Zod DTOs | | Auth / RBAC | `packages/auth/` | Better Auth + roles | | Operator UI | `apps/web/` | Next.js + Blueprint | | Reference national system | `apps/national-ref/` | Next.js issuer + connector | ## Standards alignment The data model aligns with the **AfCFTA Continental Dataset**, **WCO Data Model**: and **UN/CEFACT** electronic CoO intent. Origin criteria supported: `WP`, `SV`, `SM`, `SX`, `ST`, `SP`, `SC`. Certificate statuses: `ISSUED`, `AMENDED`, `REVOKED`, `CANCELLED`, `EXCHANGED`. See also: [Integration model](/architecture/integration-model) · [Security](/architecture/security) --- ## Integration model Path: /architecture/integration-model > **We did not build logic to integrate _into_ REC or national systems. We built the surface _for them to connect to us_.** Integration is **hub-and-spoke**: not bidirectional adapters. Every partner, whether a State Party customs system or a REC, uses the **same exchange contract**. ## Connection surface | Mechanism | Detail | | --------- | ------------------------------------------- | | Protocol | REST/JSON | | Auth | `x-api-key: eco_{test\|live}_{hex}` | | Push | `POST /api/exchange/push` | | Pull | `GET /api/exchange/out/:number` | | Payload | Defined in `packages/shared/src/schemas.ts` | ## National systems (State Parties) Certificates are registered, applied for, and issued inside national E-CO systems. The hub receives each certificate's data only after it has been issued. | Mechanism | Detail | | ------------- | --------------------------------------------------------------------------------------------------------------- | | Data model | Continental dataset clusters in `packages/shared`; national-only clusters in `packages/db` national schema | | Exchange push | `POST /api/exchange/push`, carries issued certificates, amendments, treatment status, verification, and queries | | Key scoping | API keys scoped to state party or REC | | Webhooks | Destination routing to SP webhooks (`direct`) or REC webhooks (`via_rec`) | ## REC systems (hybrid connectivity) State parties onboard with `connectivityMode` of `direct` or `via_rec`: - **direct**: hub dispatches webhooks registered for the destination state party (`statePartyId`). - **via_rec**: hub dispatches webhooks registered for the destination's REC (`recId`). The REC gateway forwards to the member state party. REC entities (COMESA, EAC, ECOWAS, SADC) are first-class in the hub with membership and REC-scoped API keys. ## Amendment routing Before the hub revokes or cancels a certificate, the importing state party must **accept** the amendment request (API message type `T2_AMENDMENT_RESPONSE`). An amendment is never applied on the exporter's say-so alone. ## End-to-end lifecycle | Step | Service | Actor | | -------------------------- | -------------------- | --------------------------- | | 1. Issue | National system | Issuing authority | | 2. Send certificate to hub | Exchange | National system via API key | | 3. Route | Webhooks | Hub → destination SP or REC | | 4. Verify | Verification | Public, customs, or trader | | 5. Monitor | Registry + analytics | Secretariat dashboard | See also: [National systems overview](/guides/national-systems/overview) · [Message types](/guides/national-systems/message-types) · [Exchange API](/api-reference/exchange) --- ## Notifications Path: /architecture/notifications The hub sends transactional alerts when State Parties are onboarded, certificates move through review, businesses are approved, and webhook routing fails. National systems do not send mail or WhatsApp themselves; they call the hub dispatch API, and the hub chooses the transport from environment configuration. ## Channels and providers | Channel | Providers | Env switch | | ------- | ---------------------------- | -------------------------------------------- | | Email | SMTP (nodemailer) or Resend | `EMAIL_PROVIDER=smtp` (default) or `resend` | | Phone | Twilio SMS or Kapso WhatsApp | `PHONE_PROVIDER=twilio` (default) or `kapso` | Defaults keep existing Secretariat SMTP and Twilio setups working. Switch providers without changing national connectors or the dispatch payload. ## Events | Event | Typical recipients | Trigger | | ----------------------- | ------------------------------- | ---------------------------- | | State party onboarded | Onboarding admin email / phone | Hub onboarding wizard | | Application submitted | Exporter | National-ref → hub dispatch | | Application approved | Exporter | National-ref → hub dispatch | | Application rejected | Exporter | National-ref → hub dispatch | | Business approved | Business owner | National-ref → hub dispatch | | Webhook routing failure | Technical primary contact point | Hub webhook delivery failure | ## Internal dispatch API National systems and the reference connector call: ```http POST /api/notifications/dispatch x-eco-notify-secret: Content-Type: application/json ``` Example body: ```json { "event": "application_approved", "email": "exporter@example.com", "phone": "+233241234567", "certificateNumber": "202607KEMU123456" } ``` Supported `event` values: `application_submitted`, `application_approved`, `application_rejected`, `business_approved`. You can also send a free-form `{ "email", "phone", "subject", "text" }` payload. `NOTIFY_INTERNAL_SECRET` must match on the hub API and the national connector. Without it, national-ref logs only (useful in local development). ## Email: SMTP or Resend ### SMTP (`EMAIL_PROVIDER=smtp`) | Variable | Purpose | | -------------------------------------------------- | -------------------- | | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS` | Nodemailer transport | | `NOTIFY_FROM_EMAIL` | From address | Required in production when `EMAIL_PROVIDER=smtp`. ### Resend (`EMAIL_PROVIDER=resend`) | Variable | Purpose | | ------------------- | --------------------------------------------------- | | `RESEND_API_KEY` | Resend API key | | `NOTIFY_FROM_EMAIL` | Verified sender (domain must be verified in Resend) | Plain-text bodies match the current SMTP templates. HTML templates can be added later without changing the dispatch contract. ## Phone: Twilio SMS or Kapso WhatsApp ### Twilio (`PHONE_PROVIDER=twilio`) | Variable | Purpose | | -------------------- | -------------- | | `TWILIO_ACCOUNT_SID` | Account SID | | `TWILIO_AUTH_TOKEN` | Auth token | | `TWILIO_FROM_NUMBER` | Sending number | Optional at boot; production fails loudly if SMS is attempted without credentials. ### Kapso WhatsApp (`PHONE_PROVIDER=kapso`) | Variable | Purpose | | -------------------------------------- | ---------------------------------- | | `KAPSO_API_KEY` | Kapso project API key | | `KAPSO_PHONE_NUMBER_ID` | Connected WhatsApp phone number ID | | `KAPSO_WEBHOOK_SECRET` | HMAC secret for inbound webhooks | | `KAPSO_TEMPLATE_ONBOARDING` | Meta utility template name | | `KAPSO_TEMPLATE_APPLICATION_SUBMITTED` | Meta utility template name | | `KAPSO_TEMPLATE_APPLICATION_APPROVED` | Meta utility template name | | `KAPSO_TEMPLATE_APPLICATION_REJECTED` | Meta utility template name | | `KAPSO_TEMPLATE_BUSINESS_APPROVED` | Meta utility template name | Outbound behavior: 1. If a template env var is set for the event, the hub sends that Meta utility template (required for business-initiated WhatsApp outside the 24-hour session window). 2. If no template is configured, the hub sends session text (fine for sandbox and open conversations). 3. Phone numbers are normalized to digits before send. ## Two-way Kapso webhooks Register this HTTPS endpoint on the Kapso phone-number webhook: ``` POST https:///api/notifications/whatsapp/webhook ``` Subscribe at least to: - `whatsapp.message.received` - `whatsapp.message.sent` - `whatsapp.message.delivered` - `whatsapp.message.read` - `whatsapp.message.failed` The hub: 1. Verifies `X-Webhook-Signature` (HMAC-SHA256 over the raw body with `KAPSO_WEBHOOK_SECRET`) 2. Deduplicates with `X-Idempotency-Key` 3. Persists each event in `whatsapp_message_events` 4. Auto-replies to inbound text with a short help message (not a chatbot) 5. Logs structured failure details when delivery fails Always returns `200` after durable accept so Kapso does not retry unnecessarily. ## Non-production behavior When the active provider credentials are missing and the app is not in full production (`NODE_ENV` + `APP_ENV`), the hub **logs** the notification instead of sending. That keeps local and staging work unblocked. ## Go-live checklist 1. Choose providers: set `EMAIL_PROVIDER` and `PHONE_PROVIDER`. 2. For Resend: verify the sending domain, set `RESEND_API_KEY` and `NOTIFY_FROM_EMAIL`. 3. For Kapso: connect a WhatsApp number, copy `KAPSO_PHONE_NUMBER_ID`, set `KAPSO_API_KEY`. 4. Approve Meta utility templates that match `KAPSO_TEMPLATE_*`. 5. Register the webhook URL and set `KAPSO_WEBHOOK_SECRET`. 6. Set `NOTIFY_INTERNAL_SECRET` on hub and national connectors. 7. Run migrations so `whatsapp_message_events` exists (`pnpm db:migrate`). 8. Trigger a test dispatch (onboarding welcome or national-ref approve) and confirm email / WhatsApp delivery. ## Related - [Security](/architecture/security) — auth model and API keys - [Pilot integration kit](/guides/pilot-integration-kit) — end-to-end pilot path - Secretariat runbook — `infra/deploy/secretariat/runbook.md` in the monorepo --- ## Security Path: /architecture/security ## Authentication model The platform uses two auth paths: | Client type | Auth method | Where they work | | ----------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- | | Hub operators (secretariat, verifier) | Session cookies via Better Auth | Hub web, registry, monitoring, admin, analytics | | National personas (exporter, issuer, authority-admin) | Session cookies via Better Auth | Reference national app (`apps/national-ref`), registration, applications, review queue, push | | Machines (national/REC systems) | API key header (`x-api-key`) | Hub `/api/exchange/*` | Issuance and business registration stay in **national systems**. The hub does not expose certificate-issue endpoints for humans. ## Human auth (Better Auth) - Hub auth routes: `/api/auth/*` on the platform API - National-ref runs its own Better Auth against the same user table (separate cookie host) - Roles: `secretariat-admin`, `authority-admin`, `issuer`, `verifier`, `exporter` - Hub demos: continue to dashboard (defaults to Secretariat Admin); switch Verifier via topbar **Viewing as** - National demos: continue to dashboard (defaults to Exporter); switch Issuer / Competent Authority via topbar; switch platforms via topbar **Platform** ## Machine auth (API keys) National and REC systems authenticate with: ``` x-api-key: eco_{test|live}_{hex} ``` | Mode | Purpose | | ------------ | ------------------------------------------------------------------------ | | `eco_test_*` | Sandbox integration, certs tagged `mode: test`, excluded from live stats | | `eco_live_*` | Production registry, real continental statistics | Keys are created by secretariat admins via `POST /api/api-keys` and shown **once** on creation. Optional `statePartyId` scopes a key to a specific State Party. See [Authentication guide](/guides/national-systems/authentication) for provisioning steps. ## Digital signing Certificates are signed through a pluggable `SigningProvider`, selected with the `SIGNING_PROVIDER` env var: | Provider | `SIGNING_PROVIDER` | Notes | | -------- | ------------------- | --------------------------------------------------------------------------------------- | | Ed25519 | `ed25519` (default) | Keys in `SIGNING_PRIVATE_KEY` / `SIGNING_PUBLIC_KEY` (required in production) | | X.509 | `x509` | PEM key + certificate for PKI-aligned deployments | | PKCS#11 | `pkcs11` | HSM-backed keys (`PKCS11_MODULE_PATH`, `PKCS11_SLOT`, `PKCS11_PIN`, `PKCS11_KEY_LABEL`) | Verification checks signature validity as part of the authenticity response. The hub's public key is exposed at `GET /api/verify/public-key` for offline verification. ## Transport security TLS is required for all production traffic. Deployed environments serve web, docs, national-ref, and the API over HTTPS. ## Rate limiting The API applies a configurable request throttle (`THROTTLE_TTL_MS`, `THROTTLE_LIMIT`; defaults 120 requests / 60s). The `infra/load-tests/` k6 suite exercises verify and exchange-push paths. ## Audit trail & monitoring Every exchange, routing, verification, onboarding, and certificate lifecycle event is written to the audit log and is queryable by Secretariat admins: - `GET /api/audit`, filterable, paginated audit trail - `GET /api/monitoring/overview`, dashboard summary - `GET /api/monitoring/exchange-messages`, exchange message log (with payload drill-down) ## Production hardening (remaining) - HSM-backed signing key custody - Security vulnerability assessment - Key rotation runbooks - Disaster recovery environment ## Related - [Notifications](/architecture/notifications) — email (SMTP / Resend) and phone (Twilio / Kapso) transport --- ## Getting started Path: /guides/getting-started ## Prerequisites - Node.js 20+ - pnpm 9+ - Docker (for PostgreSQL), or a configured remote Postgres URL in `.env` ## Install and run ```bash # From the monorepo root pnpm install docker compose up -d # skip if using remote DATABASE_URL cp .env.example .env # if not already present pnpm build:packages pnpm db:migrate pnpm seed pnpm dev ``` ### Local ports | Service | Port | | ------------------------- | ------ | | Hub web | `3000` | | Platform API | `3001` | | Documentation | `3002` | | Reference national system | `3003` | Enable demo mode in `.env` (and `apps/web/.env`, `apps/national-ref/.env`): ```bash DEMO_MODE=true NEXT_PUBLIC_DEMO_MODE=true ``` Then re-run `pnpm seed` and restart `pnpm dev`. ## Deployed environment | Service | URL | | ------------------------- | ------------------ | | Web app (central hub) | https://afcfta-eco.org | | Platform API | https://api.afcfta-eco.org | | Documentation | https://docs.afcfta-eco.org | | Reference national system | https://national.afcfta-eco.org | | Health check | https://api.afcfta-eco.org/api/health | ## Seed data `pnpm seed` creates an idempotent **rich demo dataset** (safe to re-run; upgrades party statuses on existing databases). Use `pnpm db:bootstrap` when you want a clean reset first. It includes: - **Partners**: eight state parties (GH/KE live, SN/CI/NG active, RW pilot via REC, EG inactive), five issuing authorities, REC memberships, and contact points for live parties - **Demo users** (`business@demo.eco`, `issuer@demo.eco`, `authority@demo.eco`, `admin@demo.eco`, `verifier@demo.eco`), password from `DEMO_SEED_PASSWORD` (default `Demo2026!`) - **Hub registry**: ~50 certificates (walkthrough numbers preserved), all hub certificate statuses, signed products, and exchange messages for operations/analytics - **National-ref**: approved/pending/rejected businesses, product catalog samples, national certificates in every workflow status (including a **pending cocoa application** for the issuer queue) - **Integrations**: national-ref test API key (`NATIONAL_REF_SEED_API_KEY`), sample live/revoked keys, OAuth client, webhooks with success/failure deliveries, audit timeline for Secretariat views ## Two-minute demo walkthrough Fastest demo path: **national issue → hub exchange → authentic verify → Secretariat monitoring**. On the hub sign-in page (demo mode), choose **Continue to dashboard**, or the secondary **Start guided walkthrough** for an in-app tour of the same path. Switch roles with topbar **Viewing as**; switch hub ↔ national with topbar **Platform**. 1. **Exporter (national)**: open https://national.afcfta-eco.org/sign-in → **Continue to dashboard** (or https://national.afcfta-eco.org/sign-in?demo=business). You land on **My Applications** with a seeded pending cocoa shipment. 2. **Issuer (national)**: use the persona switcher → **Issuer**: Open **Review Queue** → **Approve & push**: The national system signs the CO and exchanges issued certificate data to the hub. 3. **Verify (hub)**: open https://afcfta-eco.org/verify, paste the certificate number. You should see **Authentic**: status **EXCHANGED**: and a valid signature. 4. **Secretariat Admin (hub)**: https://afcfta-eco.org/sign-in → **Continue to dashboard** (or topbar **Platform** → **Central hub**): Open **Monitoring** to see the exchange message in the continental log. > Issuance never happens on the hub. The hub only signs (as configured), stores permanently, routes, and verifies exchanged certificate data. ## First exchange (API) 1. Sign in as Secretariat Admin on the hub (Continue to dashboard, `?demo=secretariat`, or email login), then create an API key from the admin UI, or via: ```bash curl -X POST https://api.afcfta-eco.org/api/api-keys \ -H "Content-Type: application/json" \ -H "Cookie: " \ -d '{"name": "Test National System", "mode": "test"}' ``` 2. Push an issued certificate (after national issuance): ```bash curl -X POST https://api.afcfta-eco.org/api/exchange/push \ -H "Content-Type: application/json" \ -H "x-api-key: eco_test_..." \ -d @sample-cert.json ``` 3. Pull it back: ```bash curl https://api.afcfta-eco.org/api/exchange/out/202607GHMUB8F0B9 \ -H "x-api-key: eco_test_..." ``` See [Exchange in](/guides/national-systems/exchange-in) and [Exchange out](/guides/national-systems/exchange-out) for full payload details. ## Testing ```bash pnpm test # Unit tests pnpm test:e2e # E2E (requires postgres-test on :5433) ``` ## Next steps - [National systems overview](/guides/national-systems/overview): build a connector - [Verification guide](/guides/verification): public and authority verification - [API reference](/api-reference): full endpoint listing --- ## Authentication Path: /guides/national-systems/authentication National and REC systems authenticate to exchange endpoints using API keys, not session cookies. ## Key format ``` eco_{mode}_{hex} ``` | Part | Values | | ------ | ------------------------------------------ | | `mode` | `test` or `live` | | `hex` | Random hex string (shown once on creation) | ## Creating a key Secretariat admins create keys via the platform API: ```http POST /api/api-keys Content-Type: application/json Cookie: { "name": "Kenya National CoO System", "mode": "live", "statePartyId": "" } ``` Response includes the full key **once**. Store it securely, it cannot be retrieved again. ## Using a key Send the key on every exchange request: ```http POST /api/exchange/push Content-Type: application/json x-api-key: eco_live_a1b2c3d4e5f6... ``` ## Test vs live isolation | Mode | Registry impact | Use case | | ------ | ---------------------------------------------------------- | --------------------------------- | | `test` | Certificates tagged `mode: test`; excluded from live stats | Development, UAT, partner sandbox | | `live` | Real continental registry | Production traffic | Partners should integrate against test keys first, then request a live key after UAT sign-off. ## Key scoping Optional `statePartyId` ties a key to a specific State Party. Production REC keys will use the same mechanism with REC-scoped identifiers. ## Rotation (production) Key rotation is a production operational procedure, create a new key, update the partner system, revoke the old key. Runbooks will be delivered as part of operational documentation. ## Related - [Exchange in](/guides/national-systems/exchange-in) - [Test environment](/guides/national-systems/test-environment) --- ## Building a national connector Path: /guides/national-systems/building-connector # Building a national connector Certificates of origin are **issued inside each country's own E-CO system**. When a certificate is issued, the national system **sends its data to the AfCFTA central hub**: where it is signed, stored, and routed to the destination country. The hub itself never issues certificates, it only receives, signs, and distributes them. Your job when building a connector is to make your national system send that data to the hub, and to react to messages the hub sends back. ## Reference implementation `apps/national-ref` is a working example connector you can run locally (port 3003). It shows the full flow end to end: - The national side of the workflow: business registration, product registration, certificate applications, and the issuing officer's review queue. - Sending an issued certificate to the hub with `POST /api/exchange/push` the moment it is approved. - Handling the messages that flow back through the hub, amendments, verification requests, and free-text queries, from its **hub connector** tab. Use it as a template or a UAT harness; you do not have to run it in production. ## What a connector sends and receives Everything travels through one endpoint (`POST /api/exchange/push`) using one envelope. A `messageType` field says what the message is. Use the plain names below; put the API codes on the wire. For RFP shorthand used in AfCFTA tender documents, see [Message types](/guides/national-systems/message-types). | What you're doing | `messageType` | | -------------------------------------------------------- | -------------------------- | | Sending an issued certificate to the hub | `T1_CO_DATA` | | Requesting to revoke or cancel a certificate | `T2_AMENDMENT` | | Accepting or declining an amendment | `T2_AMENDMENT_RESPONSE` | | Reporting that an import received preferential treatment | `T3_TREATMENT_STATUS` | | Asking another country to verify a certificate | `T4_VERIFICATION_REQUEST` | | Returning the result of a verification | `T4_VERIFICATION_RESULT` | | Sending or answering a free-text question | `QUERY` / `QUERY_RESPONSE` | The most important one is the **issued certificate** (`T1_CO_DATA`). Most connectors start by sending just that, then add the others as needed. ## Integration steps 1. Ask the Secretariat for a **test API key** through the onboarding wizard. 2. Save the hub URL and your API key in your national system's settings. 3. When a certificate is issued, build its data in the format defined by the [Continental Dataset](/guides/national-systems/exchange-in) and wrap it as a `T1_CO_DATA` message. 4. Send it to the hub with your key in the `x-api-key` header. Add an `Idempotency-Key` header so retries don't create duplicates. 5. Confirm it arrived by looking it up at the hub's `/verify/:certificateNumber` endpoint. ## Using the SDK The `@eco/sdk-node` client builds the envelope and headers for you, so you don't have to assemble the request by hand: ```typescript import { createEcoClient } from '@eco/sdk-node'; const client = createEcoClient({ baseUrl: 'https://api.afcfta-eco.org', apiKey: process.env.ECO_API_KEY!, }); // pushCoData(certificate, originCountry, destinationCountry, idempotencyKey) await client.pushCoData(payload, 'GH', 'KE', 'unique-idempotency-key'); ``` See the [exchange API reference](/api-reference/exchange) for every message type and its full payload. --- ## Error handling Path: /guides/national-systems/error-handling Stable partner error **codes** (`API_KEY_REQUIRED`, `CERTIFICATE_EXISTS`, …) are catalogued in [API errors](/api-reference/errors). This page is the narrative national-systems guide. ## HTTP status codes | Code | Meaning | Typical cause | | ----- | ------------ | ------------------------------------------ | | `200` | Success | Verify, exchange out (found) | | `201` | Created | Exchange push accepted | | `400` | Bad request | Duplicate certificate, validation error | | `401` | Unauthorized | Missing or invalid API key / session | | `403` | Forbidden | Insufficient role | | `404` | Not found | Certificate not in registry (exchange out) | | `500` | Server error | Unexpected failure, retry with backoff | ## Exchange in errors - **Duplicate certificate:** `400` with message `Certificate already exists in registry` - **Invalid payload:** `400` with validation details (missing fields, invalid origin criterion) ## API key errors - Missing `x-api-key` header → `401 API key required` - Revoked or unknown key → `401 Invalid or revoked API key` ## Retries & idempotency The `push` endpoint accepts an `Idempotency-Key` header so retries are safe. Recommended practice: 1. Generate a stable `Idempotency-Key` per message and reuse it on retries 2. On `5xx`, retry with exponential backoff using the same key 3. On a duplicate certificate, treat it as already-registered Webhook notifications are available for partners that register a receiver. --- ## Exchange in Path: /guides/national-systems/exchange-in Exchange in lets a national or REC system **push** a message to the central node for continental registration and distribution. The most common message is the **issued certificate itself** (API message type `T1_CO_DATA`). For plain names and RFP shorthand, see [Message types](/guides/national-systems/message-types). ## Endpoint ```http POST /api/exchange/push Content-Type: application/json x-api-key: eco_{test|live}_{hex} Idempotency-Key: ``` > `POST /api/exchange/in` is a deprecated alias of `push`. ## Envelope Every push shares the same envelope. `messageType` determines the shape of `payload`: ```json { "messageType": "T1_CO_DATA", "countryOrigin": "GH", "countryDestination": "KE", "payload": {} } ``` | Field | Required | Notes | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `messageType` | Yes | One of `T1_CO_DATA`, `T2_AMENDMENT`, `T2_AMENDMENT_RESPONSE`, `T3_TREATMENT_STATUS`, `T4_VERIFICATION_REQUEST`, `T4_VERIFICATION_RESULT`, `QUERY`, `QUERY_RESPONSE` | | `countryOrigin` | Yes | 2–3 char country code | | `countryDestination` | Yes | 2–3 char country code | | `payload` | Yes | Cluster matching `messageType` (see below) | The full contract is the `exchangePushSchema` discriminated union in `packages/shared/src/schemas.ts`. ## Issued-certificate payload (`T1_CO_DATA`) The `T1_CO_DATA` payload is the **ECO Main** cluster with an array of **ECO Products**: ```json { "messageType": "T1_CO_DATA", "countryOrigin": "GH", "countryDestination": "KE", "payload": { "applicationType": "NEW", "certificateNumber": "202607GHMUB8F0B9", "countryOrigin": "GH", "countryDestination": "KE", "exporterRegistrationNumber": "GH-EXP-00123", "exporterTpin": "TPIN-778812", "exporterName": "Accra Coffee Exporters Ltd", "exporterAddress": "12 Liberation Rd, Accra, Ghana", "consigneeName": "Nairobi Traders Co", "consigneeAddress": "48 Moi Ave, Nairobi, Kenya", "issuingOfficeCode": "GHA-CUST-01", "issuingOfficeName": "Ghana Customs. Accra", "transportDetails": "Sea freight, vessel MV Takoradi", "exportDeclaration": "EXP-2026-4471", "officerName": "A. Mensah", "approvalDate": "2026-08-14T09:30:00Z", "lastModifiedDatetime": "2026-08-14T09:30:00Z", "status": "ISSUED", "comments": "", "products": [ { "marksNumbers": "GCB-2026-01", "description": "Green coffee beans", "hsCode": "090111", "originCriteria": "WP", "quantity": 12000, "unitOfMeasure": "KG", "invoiceNumber": "INV-2026-556", "invoiceDate": "2026-08-10T00:00:00Z" } ], "signature": "optional, hub generates one if omitted" } } ``` ### ECO Main fields | Field | Required | Notes | | ----------------------------------------- | -------- | ------------------------------------------------------ | | `applicationType` | Yes | `NEW`, `ISSUED_RETROSPECTIVELY`, or `CUMULATION` | | `certificateNumber` | Yes | Format `YYYYMMCCAA######` (see below) | | `countryOrigin` | Yes | Origin country | | `countryDestination` | Yes | Destination country | | `exporterRegistrationNumber` | Yes | Exporter registration number | | `exporterTpin` | Yes | Exporter tax identification number | | `exporterName` / `exporterAddress` | Yes | Legal exporter details | | `consigneeName` / `consigneeAddress` | Yes | Consignee details | | `issuingOfficeCode` / `issuingOfficeName` | Yes | Issuing customs office | | `transportDetails` | Yes | Mode / conveyance | | `exportDeclaration` | No | Export declaration reference | | `officerName` | Yes | Approving officer | | `approvalDate` | Yes | ISO 8601 datetime | | `lastModifiedDatetime` | Yes | ISO 8601 datetime | | `status` | Yes | Certificate status (e.g. `ISSUED`) | | `comments` | No | Defaults to empty string | | `products` | Yes | At least one ECO Product line | | `signature` | No | Base64 Ed25519 signature; hub generates one if omitted | ### ECO Product fields | Field | Required | Notes | | ---------------- | -------- | ------------------------------------------- | | `marksNumbers` | Yes | Marks and numbers of packages | | `description` | Yes | Product description | | `hsCode` | Yes | HS tariff code (4–20 chars) | | `originCriteria` | Yes | `WP`, `SV`, `SM`, `SX`, `ST`, `SP`, or `SC` | | `cumulation` | No | Cumulation detail | | `quantity` | Yes | Positive number | | `unitOfMeasure` | Yes | e.g. `KG` | | `invoiceNumber` | Yes | Commercial invoice number | | `invoiceDate` | Yes | ISO 8601 datetime | ## Certificate number format Certificate numbers follow `YYYYMMCCAA######` (regex `^\d{6}[A-Z]{2}[A-Z]{2}[A-Z0-9]{6}$`): - `YYYYMM`, issue year and month - `CC`. ISO country code (uppercase) - `AA`, issuing authority code (uppercase) - `######`, 6-character alphanumeric serial Example: `202508RWMU123345`. ## Response On success, `T1_CO_DATA` returns the stored certificate (status `EXCHANGED`) with its signature and registry metadata, the same shape you get from [Exchange out](/guides/national-systems/exchange-out). Other message types return the created amendment / usage / verification / query record. ## Example ```bash curl -X POST https://api.afcfta-eco.org/api/exchange/push \ -H "Content-Type: application/json" \ -H "x-api-key: eco_test_your_key_here" \ -H "Idempotency-Key: gh-202608-0001" \ -d '{ "messageType": "T1_CO_DATA", "countryOrigin": "GH", "countryDestination": "KE", "payload": { "applicationType": "NEW", "certificateNumber": "202607GHMUB8F0B9", "countryOrigin": "GH", "countryDestination": "KE", "exporterRegistrationNumber": "GH-EXP-00123", "exporterTpin": "TPIN-778812", "exporterName": "Accra Coffee Exporters Ltd", "exporterAddress": "12 Liberation Rd, Accra, Ghana", "consigneeName": "Nairobi Traders Co", "consigneeAddress": "48 Moi Ave, Nairobi, Kenya", "issuingOfficeCode": "GHA-CUST-01", "issuingOfficeName": "Ghana Customs. Accra", "transportDetails": "Sea freight, vessel MV Takoradi", "officerName": "A. Mensah", "approvalDate": "2026-08-14T09:30:00Z", "lastModifiedDatetime": "2026-08-14T09:30:00Z", "status": "ISSUED", "products": [{ "marksNumbers": "GCB-2026-01", "description": "Green coffee beans", "hsCode": "090111", "originCriteria": "WP", "quantity": 12000, "unitOfMeasure": "KG", "invoiceNumber": "INV-2026-556", "invoiceDate": "2026-08-10T00:00:00Z" }] } }' ``` ## Tip: use the SDK The [`@eco/sdk-node`](/guides/national-systems/building-connector) client builds the envelope for you: ```typescript await client.pushCoData(payload, 'GH', 'KE', 'gh-202608-0001'); ``` See also: [Exchange out](/guides/national-systems/exchange-out) · [Exchange API reference](/api-reference/exchange) --- ## Exchange out Path: /guides/national-systems/exchange-out Exchange out lets a destination national system **pull** a certificate that was registered on the central node, typically after the origin country pushed it via exchange in. ## Endpoint ```http GET /api/exchange/out/:number x-api-key: eco_{test|live}_{hex} ``` ## Parameters | Parameter | Location | Description | | --------- | -------- | -------------------------------------------- | | `number` | Path | Certificate number (e.g. `202607GHMUB8F0B9`) | ## Response Returns the full certificate record including: - Certificate number, `applicationType`, and status - Origin / destination countries and `mode` (`test` / `live`) - Exporter and consignee details - `products` with HS codes and origin criteria - Digital signature, issue and approval dates - Related `usage`, `amendments`, `queries`, and `verifications` ## Error cases | Status | Meaning | | ------ | --------------------------------------- | | 404 | Certificate not found | | 401 | Invalid or missing API key | | 403 | Key not authorised for this certificate | ## Typical flow 1. Origin country system issues cert locally and pushes via [Exchange in](/guides/national-systems/exchange-in) 2. Central node registers cert in continental registry 3. Destination country system pulls via exchange out when goods arrive 4. Destination customs verifies via [Verification API](/api-reference/verification) ## Example ```bash curl https://api.afcfta-eco.org/api/exchange/out/202607GHMUB8F0B9 \ -H "x-api-key: eco_test_your_key_here" ``` ## Operational features (available today) - **Idempotency**: send `Idempotency-Key` on `push` for safe retries - **Webhooks**: register a receiver to be notified when messages route to your country or REC - **Rate limiting**: the API enforces a per-window request throttle See also: [Exchange in](/guides/national-systems/exchange-in) · [Exchange API reference](/api-reference/exchange) --- ## Go-live checklist Path: /guides/national-systems/go-live-checklist ## Pre go-live - [ ] Secretariat has created a **live** API key scoped to your State Party - [ ] Integration tested in **test mode** (`eco_test_*` key) against the central node test environment - [ ] Certificate payload validated against `exchangePushSchema` in `packages/shared` - [ ] Error handling implemented per [Error handling](/guides/national-systems/error-handling) - [ ] Operations team trained on key rotation and escalation contacts ## UAT sign-off - [ ] Push at least one test certificate via `POST /api/exchange/push` - [ ] Pull the same certificate via `GET /api/exchange/out/:number` - [ ] Verify the certificate appears in the central registry dashboard - [ ] Confirm test-mode certificates do not affect live registry statistics ## Production cutover 1. Replace test API key with live key (`eco_live_*`) 2. Update central node base URL to production endpoint (provided by Secretariat) 3. Run connection test from the [reference national connector](https://national.afcfta-eco.org) or your own client 4. Monitor first live exchanges via Secretariat audit log ## Post go-live - [ ] Document key storage and rotation procedure - [ ] Establish support channel with AfCFTA Secretariat TWG - [ ] Schedule periodic connectivity tests --- ## Message types Path: /guides/national-systems/message-types National systems exchange certificates and related messages with the hub through one push endpoint. Each message has a plain-language purpose, an API `messageType` code, and, in AfCFTA RFP documents only, a short label (T1–T4). > **This page is the only place in these docs that uses RFP labels (T1–T4).** Everywhere else, prefer plain language and the API codes below. | What you are doing | Plain name | API `messageType` | RFP shorthand | | -------------------------------------------------------- | ---------------------- | -------------------------- | ------------- | | Sending an issued certificate to the hub | Issued certificate | `T1_CO_DATA` | T1 | | Requesting to revoke or cancel a certificate | Amendment request | `T2_AMENDMENT` | T2 | | Accepting or declining an amendment | Amendment response | `T2_AMENDMENT_RESPONSE` | T2 | | Reporting that an import received preferential treatment | Treatment status | `T3_TREATMENT_STATUS` | T3 | | Asking another country to verify a certificate | Verification request | `T4_VERIFICATION_REQUEST` | T4 | | Returning the result of a verification | Verification result | `T4_VERIFICATION_RESULT` | T4 | | Sending or answering a free-text question | Query / query response | `QUERY` / `QUERY_RESPONSE` | - | See also: [Exchange in](/guides/national-systems/exchange-in) · [Building a connector](/guides/national-systems/building-connector) · [Exchange API](/api-reference/exchange) --- ## National systems overview Path: /guides/national-systems/overview National (State Party) systems integrate with the AfCFTA E-CO hub through a small REST surface, no custom per-country adapters. Every partner uses the same exchange contract. ## Connection model ```mermaid flowchart TB NAT[Your national CoO system] HUB["AfCFTA E-CO Central Node
POST /api/exchange/push
GET /api/exchange/out/:number"] NAT -->|"x-api-key: eco_live_…"| HUB ``` ## Integration checklist 1. **Obtain API key**: secretariat admin creates a test key for sandbox, then a live key for production 2. **Implement exchange in**: push certificates your system issues or receives 3. **Implement exchange out**: pull certificates destined for your country 4. **Test in sandbox**: use `eco_test_*` keys; verify certs appear in test mode only 5. **UAT sign-off**: validate against the reference connector and integration test suite 6. **Go live**: switch to `eco_live_*` key on production URL ## What you need to implement | Capability | Required | | --------------------------- | --------------------------------------------- | | HTTP client (REST/JSON) | Yes | | API key storage (secure) | Yes | | Certificate payload mapping | Yes, see shared Zod schema | | Retry / idempotency | Recommended, send an `Idempotency-Key` header | | Webhook receiver | Optional, to receive routed messages | ## Payload contract All partners send the same envelope defined in `packages/shared/src/schemas.ts` (`exchangePushSchema`): `{ messageType, countryOrigin, countryDestination, payload }`. The hub validates on ingest and generates a signature if one is omitted. See [Exchange in](/guides/national-systems/exchange-in) for the full issued-certificate payload. ## Provisioning note State Parties and issuing authorities are provisioned by the Secretariat during onboarding (`POST /api/state-parties/onboard`). In local/demo mode, unknown entities may be auto-created for convenience; production requires pre-registered entities. ## Guides in this section - [Message types](/guides/national-systems/message-types): plain names, API codes, RFP shorthand - [Authentication](/guides/national-systems/authentication): API key lifecycle - [Exchange in](/guides/national-systems/exchange-in): push certificates - [Exchange out](/guides/national-systems/exchange-out): pull certificates - [Test environment](/guides/national-systems/test-environment): sandbox usage --- ## Test environment Path: /guides/national-systems/test-environment Partners should always integrate against the **test environment** before requesting live API keys. ## Test mode keys Create a test key via the secretariat admin API: ```json { "name": "Ghana Integration Sandbox", "mode": "test" } ``` The returned key format is `eco_test_{hex}`. ## What test mode does | Behaviour | Detail | | ------------------- | ------------------------------------------------------------------ | | Certificate tagging | All certs created via test keys are tagged `mode: test` | | Registry stats | Test certs **excluded** from live dashboard statistics | | Data isolation | Safe to run integration tests without polluting production metrics | | Same API contract | Identical endpoints and payloads as live mode | ## Recommended workflow 1. Obtain `eco_test_*` key from secretariat admin 2. Point your connector at https://api.afcfta-eco.org (or your Secretariat-provided sandbox URL) 3. Run push/pull cycles with synthetic certificate data 4. Validate payloads against the shared Zod schema 5. Complete UAT checklist with the reference national connector 6. Request `eco_live_*` key for production cutover ## Local development When running the monorepo locally (`pnpm dev`), point your connector at the **deployed API** above for integration testing with `eco_test_*` keys, or configure a local API URL in `.env` for full offline work. ## Environment URLs (target) | Environment | Purpose | | ----------- | ------------------------------------- | | Development | Active feature work | | Test | Partner sandbox + integration testing | | Production | Live continental registry | | DR | Failover replica | Staging and production deployments exist today for web, docs, national-ref, and the API; test-mode isolation is enforced per API key within each environment. The full four-environment Secretariat model (Dev/Test/Prod/DR) is provisioned during production handover. ## Related - [Authentication](/guides/national-systems/authentication) - [Getting started](/guides/getting-started) --- ## Pilot integration kit Path: /guides/pilot-integration-kit Pilots integrate **their existing national E-CO software** with the central hub API. The Secretariat does not require pilots to run `apps/national-ref` in production, it is a reference connector for UAT and demos. ## Division of responsibility | Party | Builds | | --------------------- | ------------------------------------------------------------------------- | | **AfCFTA hub (us)** | Exchange API, signing, registry, webhooks, docs, SDK, sandbox keys | | **Pilot State Party** | Client code in their national system that calls `POST /api/exchange/push` | | **REC (optional)** | Regional routing when `connectivityMode` is `via_rec` | ## Onboarding steps (per pilot) 1. **TWG names pilot** and shares national system technical contact. 2. **Secretariat admin** runs hub onboarding (`/admin/onboarding` on https://afcfta-eco.org) → issues `eco_test_*` key. 3. **Pilot engineer** reads [Building a connector](/guides/national-systems/building-connector) and imports the Postman collection from the monorepo (`packages/shared/postman/eco-api.postman_collection.json`). 4. **Send a test certificate** from the pilot system or reference connector, then confirm it in Monitoring → exchange log. 5. **Webhook** registered for destination State Party (direct or via REC). 6. **UAT checklist** in `infra/deploy/secretariat/uat/pilot-XX-*.md` signed by pilot + TWG. 7. **Promote to live** → `eco_live_*` key for production certificate data. ## SDK quick start ```typescript import { EcoClient } from '@eco/sdk-node'; const client = new EcoClient({ baseUrl: process.env.ECO_API_URL!, apiKey: process.env.ECO_API_KEY!, }); await client.pushCoData( { applicationType: 'NEW', certificateNumber: '202607GHMUB8F0B9', countryOrigin: 'GH', countryDestination: 'KE', // …remaining continental dataset fields }, crypto.randomUUID(), // Idempotency-Key ); ``` ## Test environment - Use `eco_test_*` keys only in Test/Dev environments. - Idempotency: send `Idempotency-Key` header on every push. - Message types: issued certificates, amendments, treatment status, verification, and free-text queries, see [Exchange API](/api-reference/exchange). ## Sign-off artifacts | Artifact | Location | | ----------------------- | --------------------------------------------------------------------- | | Per-pilot UAT | `infra/deploy/secretariat/uat/pilot-*.md` | | Integration test report | `infra/deploy/secretariat/reports/pilot-integration-test-template.md` | | Go-live report | `infra/deploy/secretariat/reports/go-live-report.md` | ## Next steps - [Getting started](/guides/getting-started): local demo walkthrough - [Authentication](/guides/national-systems/authentication): key lifecycle - [Go-live checklist](/guides/national-systems/go-live-checklist) --- ## Node SDK Path: /guides/sdk Use `@eco/sdk-node` from national connectors and automation instead of hand-rolling HTTP for the happy path. ## Install ```bash pnpm add @eco/sdk-node ``` ## Quick start ```typescript import { createEcoClient } from '@eco/sdk-node'; const client = createEcoClient({ baseUrl: 'https://api.afcfta-eco.org', apiKey: 'eco_test_…', }); // Public verification const result = await client.verify('202607GHMUB8F0B9'); // Push issued certificate data await client.pushCoData(payload, 'GH', 'KE', 'idempotency-key-optional'); // Pull at destination const cert = await client.exchangeOut('202607GHMUB8F0B9'); ``` OAuth machine clients can pass `oauth: { clientId, clientSecret }` instead of `apiKey`. ## Where to go next | Need | Link | | --------------- | ---------------------------------------------------------------------------------------- | | Live try panels | [Exchange API](/api-reference/exchange), [Verification API](/api-reference/verification) | | Full contract | [OpenAPI](/api-reference/openapi) (`https://docs.afcfta-eco.org/openapi.json`) | | Error codes | [API errors](/api-reference/errors) | | Connector guide | [Building a connector](/guides/national-systems/building-connector) | National issuance is **not** on the hub SDK — issue in your national system (or the [reference connector](https://national.afcfta-eco.org)), then push. --- ## User manuals Path: /guides/user-manuals Functional documentation for operators of the AfCFTA central hub and national E-CO systems. ## Recommended demo order 1. [Exporter](/guides/user-manuals/exporter) → Business persona on the national app 2. [Issuer](/guides/user-manuals/issuer) → Government **Approve & push** 3. [Verifier](/guides/user-manuals/verifier) → public `/verify` Authentic result 4. [Secretariat](/guides/user-manuals/secretariat) → Monitoring / exchange log ## Personas | Manual | Audience | System | | ------------------------------------------------------- | ------------------- | ----------------------------------------- | | [Exporter](/guides/user-manuals/exporter) | Business / exporter | National E-CO system | | [Issuer](/guides/user-manuals/issuer) | CO issuing officer | National E-CO system | | [Authority admin](/guides/user-manuals/authority-admin) | Competent authority | National app (via Access National Portal) | | [Secretariat](/guides/user-manuals/secretariat) | AfCFTA Secretariat | Central hub | | [Verifier](/guides/user-manuals/verifier) | Customs / verifier | Hub verify + analytics | ## Architecture reminder Registration, product setup, and certificate issuance all happen in **national systems**. The central hub is an **exchange server only**: it receives, signs, stores, routes, and verifies certificates, but never issues them. --- ## Authority admin manual Path: /guides/user-manuals/authority-admin ## Responsibilities - Approve exporter business registrations - Manage issuer users for your competent authority - Monitor national issuance and hub exchange for your state party > Demo shortcut: https://national.afcfta-eco.org/sign-in?demo=authority-admin, or from the hub use topbar **Platform** → **National** then **Viewing as** → **Competent Authority**. ## Business approval Review pending registrations in the authority console (`/authority/businesses`). Approved businesses may submit CO applications. ## User management Create and manage issuer accounts linked to your issuing authority (`/authority/users`). ## Hub oversight Use the **national reference connector** for authority user management and national issuance workflows. The central hub never issues certificates itself. ## Next steps - [Exporter manual](/guides/user-manuals/exporter) - [Issuer manual](/guides/user-manuals/issuer) --- ## Exporter manual Path: /guides/user-manuals/exporter As an exporter you work entirely inside your country's **national E-CO system** (reference connector: https://national.afcfta-eco.org). There are three steps: register your business, register your products, then apply for a certificate of origin. > Demo shortcut: open https://national.afcfta-eco.org/sign-in → **Continue to dashboard** (or `?demo=business`). Seeded exporters land on **My Applications** with a pending cocoa application ready for issuer review. ## Sign in Use your national E-CO portal credentials. Exporters do **not** register on the central hub. ## Business registration 1. Complete exporter registration with trade licence, TIN, contact details, and director information. 2. Wait for authority approval before applying for certificates. ## Product registration Register each product with HS code, origin criteria, and intended export market. ## Certificate application 1. Open **New CO application** (or use a seeded pending application in demos). 2. Submit consignee and transport details. 3. Track status under **My Applications** until the issuing authority approves and pushes to the hub. ## Verification Anyone can verify an issued certificate at https://afcfta-eco.org/verify using the certificate number or QR code. ## Next steps - [Issuer manual](/guides/user-manuals/issuer): how the issuer approves and pushes - [Verification](/guides/verification): public authenticity check --- ## Issuer manual Path: /guides/user-manuals/issuer ## Role Issuing officers work in the **national E-CO system** (reference connector: https://national.afcfta-eco.org) to review applications, issue certificates, and send certificate data to the central hub. > Demo shortcut: https://national.afcfta-eco.org/sign-in?demo=government, or continue to dashboard then use topbar **Viewing as** → **Issuer**. You land on **Review Queue**. ## Review queue 1. Open pending applications in **Review Queue**. 2. Approve or reject with comments. 3. Click **Approve & push**: the system assigns a dataset-format number (`YYYYMMCCAA######`), signs the CO, and exchanges it to the hub. ## Amendments 1. Submit an amendment request (revoke or cancel) from the national system. 2. The importing state party must **accept** before the hub updates certificate status. 3. After acceptance, re-issue a replacement certificate if required. ## Hub connector settings Configure the hub API URL and `eco_test_*` or `eco_live_*` key under **Hub settings** before pushing. In local demo mode the seeded hub key is applied automatically. ## Next steps - [Verification](/guides/verification): confirm Authentic / EXCHANGED on the hub - [Exchange in](/guides/national-systems/exchange-in): machine-to-machine payload details --- ## Secretariat manual Path: /guides/user-manuals/secretariat ## Central hub operations The Secretariat admin console on https://afcfta-eco.org manages continental exchange, **not** certificate issuance. > Demo shortcut: https://afcfta-eco.org/sign-in → **Continue to dashboard** (or `?demo=secretariat`) → **Monitoring**. Use topbar **Viewing as** to switch to Verifier. ### State party onboarding 1. Run the onboarding wizard at `/admin/onboarding`: state party + issuing authority + admin user. 2. Register **contact points** for the state party, technical, business, and escalation contacts. 3. Issue test API keys; promote to live when readiness checks pass. ### REC federation Configure REC entities and set destination state parties to `direct` or `via_rec` connectivity. ### Monitoring - **Exchange log** (`/admin/monitoring`): review routing status; failed rows show `no_webhook_registered` when no webhook is configured. - Registry analytics and audit trail. ### API keys & webhooks Manage partner keys and webhook endpoints for state parties and RECs. ## Next steps - [Pilot integration kit](/guides/pilot-integration-kit) - [Go-live checklist](/guides/national-systems/go-live-checklist) - [Getting started](/guides/getting-started): two-minute demo walkthrough --- ## Verifier manual Path: /guides/user-manuals/verifier ## Public verification 1. Open https://afcfta-eco.org/verify on the central hub. 2. Enter the certificate number, scan the QR code, or upload a PDF/photo. 3. Review authenticity, signature validation, exporter/consignee, and goods lines. > After Government **Approve & push**: paste the new certificate number here. You should see **Authentic** with status **EXCHANGED**. ## Registry analytics Signed-in verifiers (https://afcfta-eco.org/sign-in?demo=verifier, or continue to dashboard then topbar **Viewing as** → **Verifier**) can open dashboard analytics at `/dashboard/analytics` for continental registry statistics. ## Treatment status and origin verification Two related flows are started by **national or importing systems** and exchanged through the hub: - **Preferential treatment**: the importing country reports that a consignment received the preferential tariff tied to a certificate. - **Origin verification**: the importing country asks the exporting country to confirm a certificate, and receives a result back. Example: Kenya customs queries Ghana about `202607GHMUB8F0B9`; Ghana replies via exchange; the hub stores both messages and can notify configured webhooks. ## Next steps - [Verification guide](/guides/verification) - [Secretariat manual](/guides/user-manuals/secretariat): continental monitoring --- ## Verification Path: /guides/verification The verification service confirms whether an exchanged E-CO is authentic, cryptographically valid, and not revoked. ## Public verification (web) | Route | Purpose | | ----------------------------- | -------------------------------------------------------- | | `/verify` | Manual certificate number entry, QR scan, or file upload | | `/verify/{certificateNumber}` | Direct link and QR scan target (live) | No authentication is required. After a national system **Approve & push**: paste the certificate number (for example `202607GHMUB8F0B9`) to see: - **Authentic** / not authentic - Status (for example `EXCHANGED`) - Exporter, consignee, issuing authority, and goods lines - Offline signature check against the hub public key An **Authentic** result confirms continental exchange worked. ## Verification API ``` GET /api/verify/:number ``` No authentication required. Returns `authentic`, `signatureValid`, `status`, and (when found) a `certificate` object with parties, products, and signature. See the [Verification API reference](/api-reference/verification) for the exact fields. ### Response states | Status | Meaning | | --------- | -------------------------------------- | | Authentic | Valid signature, not revoked | | Revoked | Certificate has been revoked | | Not found | Number not in the continental registry | ## QR codes Each issued certificate includes a QR code encoding `https://afcfta-eco.org/verify/{certificateNumber}`. Scanning opens the deep-link route; manual entry on `/verify` remains available as a fallback. ## Who verifies | Actor | Method | | ------------------- | ----------------- | | Traders / public | Web UI or QR scan | | Customs officers | Web UI or API | | Third-party systems | Verification API | ## Related - [Getting started](/guides/getting-started): two-minute demo walkthrough - [Verification API reference](/api-reference/verification) --- ## Writing for E-CO documentation Path: /guides/writing-for-eco-docs This page is the **writing contract** for human and agent authors updating the public documentation site. ## Document types (Diátaxis) | Type | Question it answers | Examples in this site | | --------------- | ---------------------------- | ---------------------------------------------------------------------------------------- | | **Tutorial** | How do I get a first result? | [Getting started](/guides/getting-started) | | **How-to** | How do I accomplish a task? | [Building a connector](/guides/national-systems/building-connector) | | **Explanation** | Why is it designed this way? | [Integration model](/architecture/integration-model) | | **Reference** | What is the exact contract? | [API reference](/api-reference), [Message types](/guides/national-systems/message-types) | Pick one primary type per page. Link across types instead of mixing tutorial prose into reference tables. ## Integration truths (repeat everywhere relevant) 1. **Hub, not issuer** — national and REC systems issue; the continental hub only exchanges, stores, routes, and verifies. 2. **Same REST contract** for State Parties and RECs; routing may be `direct` or `via_rec`. 3. **Sandbox isolation** — `eco_test_*` keys and test data must not pollute live registry statistics. 4. **URLs in source** — use `https://afcfta-eco.org`, `https://api.afcfta-eco.org`, `https://docs.afcfta-eco.org`, and `https://national.afcfta-eco.org` in markdown; never `localhost` or hardcoded production hosts in committed content. 5. **API reference language** — functional docs in six AU languages; OpenAPI narrative and Try panels are English-first. ## Authoring mechanics - Frontmatter: `title` and `description` only. - Navigation: every new page must appear in `content/meta.json` and exist for **all six locales** (`en`, `fr`, `ar`, `pt`, `es`, `sw`). - Run `pnpm i18n:check` in `apps/docs` before merging. - Diagrams: fenced ` ```mermaid ` blocks where a flow beats prose. ## Agent and llms surfaces When you change public integration behaviour, update in the **same change**: - The relevant guide or API reference page - Curated [`/llms.txt`](/llms.txt) (generated from `@eco/shared`) - Agent corpus pages under [`/agents/`](/agents) See `.cursor/rules/docs-and-llms.mdc` in the monorepo for the full checklist. ## Tone - Write for **TWG reviewers, national integrators, and Secretariat operators** — precise, procurement-safe, no marketing fluff. - Prefer **decision tables** over long bullet lists when comparing paths (sandbox vs live, push vs pull, verify online vs offline). --- ## Origin criteria Path: /standards/origin-criteria Each goods line on a certificate must declare an **origin criterion**: the rule under which the product qualifies as originating in the exporting State Party. ## Supported criteria These are the AfCFTA Continental Dataset origin criteria codes (`ORIGIN_CRITERIA` in `packages/shared/src/constants.ts`), validated on every product line at exchange: | Code | Name | | ---- | -------------------------------------------------------- | | WP | Wholly obtained | | SV | Substantial transformation – Value Added Content | | SM | Substantial transformation – Material Content | | SX | Substantial transformation – Change of Tariff Heading | | ST | Substantial transformation – Change of Tariff Subheading | | SP | Substantial transformation – Process Rule | | SC | Substantial transformation – Cumulation | ## Usage in payloads The criterion is set per product line via the `originCriteria` field: ```json { "products": [ { "marksNumbers": "GCB-2026-01", "description": "Green coffee beans", "hsCode": "090111", "originCriteria": "WP", "quantity": 12000, "unitOfMeasure": "KG", "invoiceNumber": "INV-2026-556", "invoiceDate": "2026-08-10T00:00:00Z" } ] } ``` ## Standards alignment The AfCFTA E-CO data model aligns with: - **WCO Data Model**: entity and field naming intent - **UN/CEFACT**: electronic certificate of origin structure Full standards mapping documentation will be expanded during production delivery. ## Related - [Exchange in](/guides/national-systems/exchange-in): goods line schema - [Hybrid system](/architecture/hybrid-system): data model overview