# Integration Guide — Partner API
> The full flow to integrate Firma Digital as a partner — issue purchase_codes, redeem identity, and retrieve the certificate.
This guide covers the full lifecycle of a purchase_code: issuing it, redeeming it (validating the holder's identity), and retrieving the already-issued certificate. There are two separate APIs, on two different servers:
- **Purchase Code API** (`partner-pcode`, Cloudflare Worker, `/api/partner/v1`) — generates and lists purchase_codes.
- **Partner API** (`/api/partners/v1`) — redeems identity and delivers certificates.
## 1. Your configuration decides the flow, not the endpoint
Unlike previous versions of this API, **there is a single redeem endpoint** (`certificate_request`). Its behavior adapts automatically based on your partner configuration:
| Your configuration | What happens in `certificate_request` |
| --- | --- |
| `require_cedula_serie = 0` (full flow, default) | You must send `names`/`fLastname`/`phone`. Validated against Clave Única in addition to the civil registry. |
| `require_cedula_serie = 1` (reduced flow) | **Don't** send `names`/`fLastname`/`phone` — they come from the civil registry. You must send `serialNumber` (ID card serial). No Clave Única. |
| `mode = 1` (B2C, default) | The certificate is only delivered once you call `certificate_claim`. |
| `mode = 2` (B2B) | The certificate is generated and delivered automatically (webhook/email) in the same `certificate_request` call — **don't call `certificate_claim`**, it will always say "already issued". |
These two settings are independent — you can have reduced flow + B2B, full flow + B2C, or any other combination. Contact support to configure them.
## 2. B2C flow (the most common one)
1. **Issue a purchase_code** — `POST /pcode` on the Purchase Code API.
```bash
curl -X POST https://partner-pcode.tsp.workers.dev/api/partner/v1/pcode \
-H "x-gw: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"productCode": "FV01", "external_reference_id": "order-12345"}'
```
This call can fail with `400` if your partner doesn't have that `productCode` enabled — each partner can be restricted to a subset of products (`FV00`-`FV03`). If you're not sure which products you have access to, contact support.
2. **Redeem the purchase_code** — `POST /certificate_request`. Which fields you send depends on your flow (see table above). Example with the reduced flow:
```bash
curl -X POST https://api.firma.digital/api/partners/v1/certificate_request \
-H "x-gw: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"pCode": "MYPARTNER-26-FV01-A1B2C3D4",
"referenceId": "order-12345",
"dni": "18925910-7",
"serialNumber": "536463312",
"email": "user@example.com",
"pin": "1234"
}'
```
The response comes back with `status: "VALIDATED"` — the certificate **doesn't exist yet**, only identity was verified.
3. **Retrieve the certificate** — `POST /certificate_claim`, with the same `pin` used in the previous step (it doubles as the PFX's password).
```bash
curl -X POST https://api.firma.digital/api/partners/v1/certificate_claim \
-H "x-gw: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"p_code": "MYPARTNER-26-FV01-A1B2C3D4", "dni": "18925910-7", "pin": "1234"}'
```
This call is **one-time only**: calling it again responds `201` with `ALREADY_ISSUED` instead of regenerating the certificate. If you need to recover it after it was already claimed, contact support.
## 3. B2B flow
Same as steps 1 and 2 above, except step 2 is already the last one: if your configuration has `mode = 2`, the `certificate_request` response comes back with `status: "EMITTED"` and the certificate is delivered automatically through your configured webhook and/or email — both can fire at once, it's not either/or. There is no step 3.
## 4. Checking status
`GET /status?p_code=...` — most useful if you integrated via the **widget** (the user completes the flow in their own browser, not in a single call you make) and you need to know how far along they are without listening to widget events in real time.
```bash
curl "https://api.firma.digital/api/partners/v1/status?p_code=MYPARTNER-26-FV01-A1B2C3D4" \
-H "x-gw: $API_KEY"
```
Possible states, in precedence order (the first one that applies wins):
| State | Meaning |
| --- | --- |
| `CANCELLED` | Voided (order cancelled) — wins even over an already-issued certificate. |
| `EXPIRED` | The certificate was issued but has since expired. |
| `EMITTED` | Certificate generated and delivered. |
| `LOCKED` | Blocked before it got issued — if it was already issued, this doesn't show: it still reports `EMITTED`. |
| `IN_ONBOARDING` | The user is midway through the widget flow. |
| `VALIDATED` | Identity verified (B2C only), `certificate_claim` still pending. |
| `ACTIVE` | Exists, nobody has redeemed it yet. |
| `NOT_FOUND` | The purchase code doesn't exist. |
## 5. Common errors
| Code | Cause |
| --- | --- |
| `401` | Invalid or missing API Key. |
| `404` | The purchase_code doesn't exist (or, in `certificate_claim`, exists but never went through `certificate_request`). |
| `406` | Business rejection — the `message` field says which: serial required, RUT mismatch, duplicate email, ID card doesn't match the civil registry, expiration date mismatch, etc. |
| `423` | The purchase_code is permanently blocked (voided). |
| `429` | Rate limit — 10 emissions per minute per partner, or ID validation attempts exhausted (temporary security lock). |
See the [Partner API Reference](/en/api/partners/) and the [Purchase Code API Reference](/en/api/pcode/) for full details on each endpoint.