# Business introducer

import { ApiExample, ApiResponse } from "../../../src/ApiExample";

A **business introducer** brings in and manages a portfolio of client companies on Instafuel. You build your own interface; Instafuel provides the API and executes the money movements.

This page describes the model. The other guides cover the mechanics.

## The portfolio

Your portfolio is the set of companies attached to your account. Three properties matter:

**Exclusive.** A company belongs to **one** introducer at any point in time. There is no shared portfolio.

**Derived from the key.** Your calls are automatically restricted to your portfolio. No parameter lets you step outside it — see [Scope](/en/guides/perimetre).

**Not yours to change.** You add a company by onboarding it. You cannot detach one, nor claim one that belongs to another introducer: those are Instafuel operations.

## What you can do

| Area | Capability | Required scope |
|---|---|---|
| Read | portfolio, wallets and ledger, transactions, drivers, alerts | `COMPANIES_READ` … `ALERTS_READ` |
| Onboarding | create a client company and its first fleet manager account | `COMPANIES_WRITE`, `USERS_WRITE` |
| Client accounts | create and update fleet manager and finance director accounts | `COMPANIES_WRITE`, `USERS_WRITE` |
| Money | **request** a wallet credit with proof — see [Wallet credit](/en/guides/credit-wallet) | `CREDIT_REQUEST_WRITE` |
| Reports | trigger an export and fetch the produced file | `REPORTS_READ` |

Out of reach: stations, fuel brands, platform settings, debits, wallet blocks, and any data of a company that is not in your portfolio.

## Onboarding a company

A single call does three things: it creates the company, attaches it to your portfolio, and creates its initial fleet manager account — which receives its activation link.

<ApiExample
  method="POST"
  path="/v1/papi/companies"
  idempotency
  body={{
    name: "Transports Kouassi SARL",
    email: "contact@kouassi.ci",
    phoneNumber: "+2250700000001",
    rccm: "CI-ABJ-2024-B-12345",
    nif: "1234567A",
    headquartersAddress: "Zone 4, Marcory, Abidjan",
    fleetAdministrator: {
      firstName: "Awa",
      lastName: "Kouassi",
      email: "awa.kouassi@kouassi.ci",
      phoneNumber: "+2250700000002",
    },
  }}
/>

<ApiResponse
  status={201}
  body={{
    id: 4832,
    ref: "ENT-B14E77A3",
    name: "Transports Kouassi SARL",
    email: "contact@kouassi.ci",
    phoneNumber: "+2250700000001",
    rccm: "CI-ABJ-2024-B-12345",
    nif: "1234567A",
    headquartersAddress: "Zone 4, Marcory, Abidjan",
    status: "ACTIVE",
    walletBalanceFcfa: 0,
    fleetAdministrator: {
      id: 9114,
      ref: "USR-2D80F5C7",
      firstName: "Awa",
      lastName: "Kouassi",
      email: "awa.kouassi@kouassi.ci",
      role: "FLEET_ADMINISTRATOR",
      status: "PENDING_ACTIVATION",
      activationExpiresAt: "2026-09-03T10:22:05.000Z",
    },
    createdAt: "2026-08-31T10:22:05.000Z",
  }}
/>

A company already known — same RCCM or same email — is refused rather than duplicated:

<ApiResponse
  status={409}
  body={{
    error: {
      code: "COMPANY_ALREADY_EXISTS",
      message: "Une entreprise avec ce RCCM existe déjà",
      conflictingField: "rccm",
    },
  }}
/>

<Callout type="caution" title="Attachment is not negotiable from the body">
  An introducer identifier sent in the body is ignored. The company is attached to the portfolio of the key you used, always.
</Callout>

### Email is mandatory

No web account is created with a password. The fleet manager receives a **single-use activation link, valid for 72 h** — hence the `status: "PENDING_ACTIVATION"` and `activationExpiresAt` in the response. Without a valid email address, the account exists but can never sign in.

Check the address before the call: a typo means a blocked client and an account to recreate.

## Reading the portfolio

```http title="Endpoints"
GET /v1/papi/companies                     # companies in the portfolio
GET /v1/papi/companies/4832                # a single company
GET /v1/papi/companies/4832/wallet         # balance and commitments
GET /v1/papi/companies/4832/wallet/ledger  # wallet entries
GET /v1/papi/wallets                       # balances across the portfolio
GET /v1/papi/transactions                  # transactions across all companies
GET /v1/papi/transactions/{id}             # a single fill-up
GET /v1/papi/drivers                       # driver wallets
GET /v1/papi/alerts                        # open alerts for your clients
GET /v1/papi/reports/portfolio-summary     # figures for a period
```

Every one of these routes is restricted to your portfolio. A `companyId` parameter narrows it **further**; outside the portfolio it returns `404` — the API does not confirm the existence of a company that is not yours.

<ApiExample
  path="/v1/papi/companies"
  query={{ search: "Kouassi", page: 1, perPage: 20 }}
/>

<ApiResponse
  status={200}
  body={{
    data: [
      {
        id: 4832,
        ref: "ENT-B14E77A3",
        name: "Transports Kouassi SARL",
        email: "contact@kouassi.ci",
        phoneNumber: "+2250700000001",
        rccm: "CI-ABJ-2024-B-12345",
        nif: "1234567A",
        headquartersAddress: "Zone 4, Marcory, Abidjan",
        status: "ACTIVE",
        walletBalanceFcfa: 1450000,
        driversCount: 8,
        vehiclesCount: 6,
        createdAt: "2026-02-11T09:14:02.000Z",
      },
      {
        id: 4790,
        ref: "ENT-3F71B8D2",
        name: "Bâtiments Diallo & Fils",
        email: "compta@diallo-btp.ci",
        phoneNumber: "+2250505000042",
        rccm: "CI-ABJ-2023-B-88120",
        nif: "9982104B",
        headquartersAddress: "Yopougon Zone Industrielle, Abidjan",
        status: "ACTIVE",
        walletBalanceFcfa: 320000,
        driversCount: 3,
        vehiclesCount: 3,
        createdAt: "2026-01-28T15:02:47.000Z",
      },
    ],
    pagination: {
      page: 1,
      perPage: 20,
      total: 12,
      totalPages: 1,
    },
  }}
/>

A company's wallet is read separately, with the detail of what is committed:

<ApiExample path="/v1/papi/companies/4832/wallet" />

<ApiResponse
  status={200}
  body={{
    companyId: 4832,
    ref: "WAL-6E51A9C4",
    balanceFcfa: 1450000,
    heldFcfa: 62000,
    availableFcfa: 1388000,
    status: "ACTIVE",
    lastEntryAt: "2026-08-30T07:41:19.000Z",
  }}
/>

`availableFcfa` is what the company's drivers can actually spend: the balance minus open fill-ups (`heldFcfa`). That is the figure to display, not `balanceFcfa`.

## Managing your clients' accounts

```http title="Endpoints"
GET  /v1/papi/companies/4832/users
POST /v1/papi/companies/4832/users
PUT  /v1/papi/companies/4832/users/9114
```

Two roles can be created: **fleet manager** (`FLEET_ADMINISTRATOR`) and **finance director** (`DAF`).

<ApiExample
  method="POST"
  path="/v1/papi/companies/4832/users"
  idempotency
  body={{
    firstName: "Serge",
    lastName: "Bamba",
    email: "serge.bamba@kouassi.ci",
    phoneNumber: "+2250707000123",
    role: "DAF",
  }}
/>

<ApiResponse
  status={201}
  body={{
    id: 9127,
    ref: "USR-45C1E0B8",
    firstName: "Serge",
    lastName: "Bamba",
    email: "serge.bamba@kouassi.ci",
    phoneNumber: "+2250707000123",
    role: "DAF",
    companyId: 4832,
    status: "PENDING_ACTIVATION",
    activationExpiresAt: "2026-09-03T11:40:12.000Z",
  }}
/>

Any other `role` value is refused:

<ApiResponse
  status={422}
  body={{
    error: {
      code: "ROLE_NOT_ALLOWED",
      message: "Rôle non autorisé à la création d'un compte client",
      allowedRoles: ["FLEET_ADMINISTRATOR", "DAF"],
    },
  }}
/>

The account created belongs to the **company**, not to you: it gets no access to the partner API. It receives its own activation link and signs in to the Instafuel dashboard.

The split between the two roles is deliberate: the fleet manager runs operations (vehicles, drivers, documents), the finance director runs the money (caps, top-ups, reports). See [Roles](/en/reference/roles).

## Updating a company

<ApiExample
  method="PUT"
  path="/v1/papi/companies/4832"
  idempotency
  body={{
    email: "finance@kouassi.ci",
    phoneNumber: "+2250700000003",
    headquartersAddress: "Zone 4C, Rue du Canal, Marcory, Abidjan",
  }}
/>

Editable fields: `name`, `email`, `phoneNumber`, `rccm`, `nif`, `headquartersAddress`.

Any other field sent is ignored server-side. You cannot move a company to another portfolio, touch its wallet, or delete it.

## Reports

A single, **synchronous** endpoint: the portfolio summary over a period. There is no file export on this API — an ERP wants numbers to aggregate, not a spreadsheet to parse.

<ApiExample
  path="/v1/papi/reports/portfolio-summary"
  query={{ from: "2026-08-01", to: "2026-08-31" }}
/>

<ApiResponse
  status={200}
  body={{
    period: { from: "2026-08-01T00:00:00.000Z", to: "2026-08-31T23:59:59.000Z" },
    totals: {
      companies: 14,
      activeCompanies: 12,
      transactions: 318,
      amountFcfa: 9482500,
      walletBalanceFcfa: 13942000,
      walletHeldFcfa: 214000,
    },
    companies: [
      {
        id: 4832,
        ref: "ENT-B14E77A3",
        name: "Transports Kouassi SARL",
        status: "ACTIVE",
        transactions: 47,
        amountFcfa: 1842300,
        walletBalanceFcfa: 1450000,
      },
    ],
  }}
/>

Without `from` and `to`, the period covers the **last 30 days**. Only confirmed transactions are counted: an open fill-up does not land in `amountFcfa`, but weighs in `walletHeldFcfa`.

For a file export, use the partner portal: the machine surface only produces JSON.

## What you cannot do

| Operation | Why |
|---|---|
| Credit a wallet directly | you are a third party; only Instafuel observes the incoming payment — see [Wallet credit](/en/guides/credit-wallet) |
| Debit, refund, block a wallet | Instafuel operations |
| Detach a company from your portfolio | Instafuel operation |
| Delete a company or a client account | financial data is retained; accounts are deactivated, not deleted |
| See stations, fuel brands, prices | outside the introducer model |
