# Scope

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

The partner API exposes one surface, `/v1/papi/*`. Your key gives access to **your portfolio, and nothing else**.

## The rule

<Callout type="note" title="Scope comes from the key, never from the request">
  No operation accepts a scope identifier from the body or the query to **widen** what you can see.

  A `companyId` parameter is always a **narrowing** filter: it is intersected with your actual portfolio. If it falls outside, the answer is `403` — never silence, never someone else's data.
</Callout>

With a portfolio holding companies 4832 and 4790, an unfiltered call covers both:

<ApiExample path="/v1/papi/transactions" query={{ perPage: 2 }} />

<ApiResponse
  status={200}
  body={{
    data: [
      {
        ref: "TRX-4B71E0A9",
        companyId: 4832,
        amountFcfa: 31875,
        status: "CONFIRMED",
        createdAt: "2026-08-30T07:41:19.000Z",
        confirmedAt: "2026-08-30T07:41:19.000Z",
      },
      {
        ref: "TRX-9D02C15E",
        companyId: 4790,
        amountFcfa: 26400,
        status: "CONFIRMED",
        createdAt: "2026-08-29T16:05:52.000Z",
        confirmedAt: "2026-08-29T16:05:52.000Z",
      },
    ],
    pagination: {
      page: 1,
      perPage: 2,
      total: 318,
      totalPages: 159,
    },
  }}
/>

A `companyId` from the portfolio narrows the view — `200`, company 4832 only:

<ApiExample path="/v1/papi/transactions" query={{ companyId: 4832 }} />

A `companyId` outside the portfolio is refused, without leaking anything about the company:

<ApiExample path="/v1/papi/transactions" query={{ companyId: 5001 }} />

<ApiResponse
  status={404}
  body={{
    error: {
      code: "COMPANY_NOT_FOUND",
      message: "Entreprise introuvable",
    },
  }}
/>

Note the `404`: the response does not say whether company 5001 exists. A `403` would have confirmed it, and a stolen key could then have enumerated the platform's companies.

Same principle on writes: scope fields present in a request body are **ignored**, not honoured. Onboarding a company always attaches it to *your* portfolio, whatever introducer identifier you send.

## Three levels of restriction

A request goes through three successive filters. Each can refuse it:

| Level | Question | Refusal |
|---|---|---|
| Key | is the key valid, active, called from an allowed IP? | `401`, `403 PAPI_IP_FORBIDDEN` |
| Scopes | does the key carry the scope this operation needs? | `403 PAPI_SCOPE_MISSING` |
| Portfolio | does the target resource belong to your portfolio? | `404 COMPANY_NOT_FOUND` |

Scopes (`COMPANIES_READ`, `COMPANIES_WRITE`, `CREDIT_REQUEST_WRITE`, `REPORTS_READ`) are covered in [API keys](/en/guides/cles-api).

## `403` or `404`?

The rule is simple, and has no exception on this API:

- **`403`** — you are identified, but the **key** lacks the requested scope, or the call comes from an unauthorised address, or your account is closed. The problem is your authorisation.
- **`404`** — the resource does not exist **or** is not in your portfolio. Both cases are deliberately indistinguishable.

A `404` on an identifier you believe valid therefore usually means it belongs to someone else. This is deliberate: probing the API must not let anyone map other people's data.

## What is not exposed

The partner API gives no access to stations, fuel brands, platform settings, or the data of a company that has left your portfolio. Those surfaces exist for other actors and are not public.

An operation that does not appear in the [API reference](/api) is not reachable with an `ifp_*` key, whatever URL you try.

## What your clients see on their side

The accounts you create for your clients — fleet manager, finance director — sign in to the Instafuel dashboard with their own password. They see their company, not your portfolio, and have no access to the partner API.

Conversely, your key does not let you act *as* one of those accounts. See [Roles](/en/reference/roles).
