Requirements
GET /api/v1/partner/customers/{customerId}/requirements
POST /api/v1/partner/customers/{customerId}/requirements
GET /api/v1/partner/customers/{customerId}/requirements/{requirementId}
PATCH /api/v1/partner/customers/{customerId}/requirements/{requirementId}
Scopes:
kyb:readto read,kyb:writeto raise or change. NoIdempotency-Key— a duplicate requirement is visible, harmless and cancellable.
A requirement is one thing still outstanding on a KYB case, after it was submitted.
Before this existed, the only way to ask for anything post-submission was to
reject the whole case: you received one coarse rejection_reason, so “an
identity document for the second UBO” arrived as documents_missing and you had
to guess. A requirement names its own target, carries its own status and
resolves on its own — so several can be open at once and each is a row in your
UI the merchant can act on.
An open requirement reopens the case
This is the point. Asking for one more document must not mean rejecting an entire application, and a merchant who cannot edit cannot supply what was asked for.
While anything is open or fulfilled:
POST /verificationsaccepts edits againPOST /documentsaccepts evidence again
An approved case does not reopen. Documents on an approved merchant can still be replaced through the normal upload path.
The object
{
"requirement_id": "102b4aa0-05f2-439a-88fd-aabb0adf6e7d",
"kind": "document",
"target": "ubo:1",
"document_type": "director_id",
"message": "Identity document for the second UBO",
"status": "open",
"raised_by": "you",
"document_id": null,
"due_at": null,
"created_at": "2026-08-27T12:28:19.696Z",
"updated_at": null,
"resolved_at": null
}| Field | Notes |
|---|---|
kind |
document · field · clarification |
target |
Your vocabulary — ubo:1, director:0, field:registration_number. Opaque to us: the party that raised it is the party that knows what it points at. |
document_type |
Only when kind is document, and optional. One of the five named types, or omit it — see below. |
message |
What the merchant is shown. Max 500 characters, rejected rather than truncated. |
raised_by |
you · partner · niftipay. Never another partner’s client id — which competitor also serves this merchant is not your business. |
due_at |
Optional ISO-8601. Advisory; nothing enforces it. |
Asking for a document we have no name for
document_type is optional. Omit it and the requirement is simply “a document”,
described by your message:
{
"kind": "document",
"target": "company",
"message": "Shareholder register, dated within 3 months"
}The merchant uploads it with document_type: "other".
other is accepted on upload and is never required at submit — the five
named types are what submission checks for, and adding a sixth there would make
it mandatory for every merchant on the platform. So an other document satisfies
your requirement without changing what completeness means.
Statuses, and who may set them
open ──▶ fulfilled ──▶ satisfied
│
└──────────────────▶ cancelled
| Status | Meaning |
|---|---|
open |
The merchant has to do something. |
fulfilled |
They did. The party that raised it has not accepted yet. |
satisfied |
Accepted. Closed. |
cancelled |
Withdrawn. Closed. |
The asymmetry is deliberate. The side providing evidence may set
fulfilled and may not set satisfied. Only the party that raised a
requirement accepts or cancels it — otherwise you could close your own
requirements and carry an unreviewed case to submission.
A closed requirement never reopens. Raise a new one.
Attempting a transition you are not entitled to is 409 invalid_state, with
details[0] naming the from and to.
Raising one
curl -X POST https://www.niftipay.com/api/v1/partner/customers/pc-1/requirements \
-H "Authorization: Bearer <partner_api_key>" \
-H "Content-Type: application/json" \
-d '{ "kind": "document",
"target": "ubo:1",
"document_type": "director_id",
"message": "Identity document for the second UBO",
"due_at": "2026-09-10T00:00:00.000Z" }'
message is required — it is what the merchant reads, and a requirement nobody
can read is not a requirement. Max 50 open requirements per customer.
Listing
?status=open returns only what is outstanding (open and fulfilled).
Without it you get everything, oldest first — a merchant works down a list, and
what was asked for first is usually what blocks the rest.
Webhooks
requirement.created and requirement.resolved carry the same fields as the
object, including message. They fan out to every partner covering the merchant,
including the one that raised it — a partner with more than one system
integrated should not have to special-case its own writes.
Errors
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | Bad kind, missing/over-long message, bad document_type, unparseable due_at. |
| 403 | insufficient_scope | Token lacks kyb:read / kyb:write. |
| 404 | resource_not_found | Not your customer, no verification started, or no such requirement. |
| 409 | invalid_state | Transition not allowed for this caller, or the requirement is closed. |
| 409 | limit_reached | 50 open requirements already. |
| 503 | temporarily_unavailable | Transient. Retry. |