# 2 · Deposit reporting

Phase 2 of the flow. Felix sends USDC to Nexa's BitGo custody wallet. Tesser
holds the keys to that wallet, observes the arrival, and **reports it** to
Nexa together with the risk screening results from Tesser's core platform.

<Mermaid
  chart={`sequenceDiagram
    autonumber
    participant T as Tesser (orchestrator)
    participant N as Banco Nexa

    Note over T,N: Phase 2 · USDC deposit (Felix → BitGo, observed by Tesser)
    T->>N: POST /v1/quotes/{tesser_quote_id}/deposits [N]<br/>(usdc_amount, risk screening, tx hashes)
    N-->>T: 200 DEPOSIT_REPORTED (acknowledgment only)`}
/>

## Step 3 · Report the USDC deposit <N />

**Operation:** [`POST /v1/quotes/{tesser_quote_id}/deposits`: Report USDC deposit (op 3)](/api-nexa/deposits#report-usdc-deposit-op-3)
· hosted by **Nexa**, called by **Tesser**.

**This is a report, not a request.** Tesser attests facts: USDC arrived, in
this amount, via these transactions, with these risk screening results. Nexa
alone decides what to do with the report (credit, request additional funds,
or reject) and that decision arrives **asynchronously via webhook**
([phase 3](/flow-nexa/credit-decision)).

Consequently this endpoint has **no synchronous risk or amount errors**. A
2xx means "report recorded", nothing more. It does not mean Nexa accepted the
amount, and it does not mean the risk result was satisfactory.

**Tesser always reports a deposit it observes, including when risk screening
rejects.** The compliance outcome travels in the payload; it never suppresses
the report.

### The report payload

| Field | Notes |
| --- | --- |
| `tesser_deposit_id` | UUIDv4 minted by Tesser; idempotency key for *this report*. |
| `usdc_amount` | What actually arrived in this report, which is not necessarily the quoted `usdc_amount`. |
| `risk_status` | Tesser core-API enum, verbatim: `unchecked`, `awaiting_decision`, `automatically_approved`, `manually_approved`, `automatically_rejected`, `manually_rejected`. |
| `risk_status_reasons` | Tesser core-API `RiskStatusReason` objects, verbatim. `risk_status_reason_type` is one of `ENTITY`, `ACCOUNT`, `OWNERSHIP`, `COUNTERPARTY`, `INDIRECT`, `BEHAVIORAL`, `ERROR`, plus a category and a message. |
| `transaction_hashes` | The on-chain transaction(s). This is the field both ops teams reconcile against the chain with when something disagrees. |

Those two fields are Tesser core-API types used verbatim, so there is one
compliance dictionary across both companies.

### Repeatable by design

Each report is a new sub-resource, so a quote can carry several. That is
what makes the additional-funds flow work. The response's
`usdc_total_reported` is the sum across every report on the quote.

**Late reports** (a report arriving when the quote is already `CREDIT_SENT`,
`CREDIT_CONFIRMED` or `PAYMENT_*`, for example when Felix sends extra funds
after crediting) return `200`, are recorded with no state change, appear in the
[quote GET](/api-nexa/quotes#get-quote-optional-op-8)'s `deposits` array, and
are flagged for ops review on both sides. The always-report rule has no
exception, so this case has defined success semantics. What Nexa *does* with
late funds is part of [open question 3](/open-questions).

### Rejection is not an undo

The USDC has already moved on-chain before this endpoint is ever called. A
later `quote.rejected` decision means "Nexa will not credit GTQ", not "the
deposit didn't happen". Disposition of the received USDC (return it? hold it
pending review?) is operational and out-of-band for the POC
([open question 4](/open-questions)).

<div className="what-can-go-wrong">

**What can go wrong: report USDC deposit**

| `error_code` | HTTP | When |
| --- | --- | --- |
| `VALIDATION_ERROR` | 400 | Malformed amount, bad UUID, missing risk fields. |
| `UNAUTHENTICATED` | 401 | Client certificate or customer API key missing/invalid. |
| `QUOTE_NOT_FOUND` | 404 | No quote with this `tesser_quote_id`. |
| `QUOTE_NOT_ACCEPTED` | 409 | The quote is still `CREATED`. Accept it first. |
| `QUOTE_EXPIRED` | 409 | The quote reached `EXPIRED` before any deposit was reported. |
| `QUOTE_REJECTED` | 409 | The quote is in `REJECTED`. A new quote is the recovery path. |
| `DEPOSIT_CONFLICT` | 409 | Same `tesser_deposit_id`, different body. |
| `INTERNAL_ERROR` | 500 | Unexpected failure on Nexa's side. |

There is deliberately **no** risk-rejection error and **no** amount-mismatch
error here. Both are decisions, and decisions arrive by webhook.

</div>

**Next:** [3 · Credit decision](/flow-nexa/credit-decision).
