Driving your certification scheme
You run a certification scheme, and companies apply for it on BeCause: they fill in your application template, submit, and wait for your verdict. Your own certification tool — the system where your auditors actually work — wants those applications without anyone retyping them. This walkthrough is the complete loop: hear about changes over webhooks, read the application and its answers, and write your decisions back so the applicant sees them on BeCause. It is the same surface BeCause's own certifier integrations run on — nothing here is bespoke.
Applications themselves are created on BeCause — by your invitations and by companies claiming them — not through this API. Everything below reads and updates applications that already exist.
Before you start
- An API key for your certifier profile — the profile that owns the application templates. Getting started covers creating one, Authentication what it acts as. Every call below is scoped to that profile: applications owned by anyone else read as if they did not exist.
- A webhook subscription for the two application topics — created on the Webhooks page while signed in as the certifier profile. Webhooks covers configuration and signature verification; the application-specific payloads are below.
The application lifecycle
An application moves through statuses, and the API speaks them as both a number and a name — status and statusName in every read, the number in every write:
status | statusName | Phase |
|---|---|---|
| 100 | Open | Created, applicant not yet filling it in |
| 150 | InDataCollection | Applicant answering |
| 200 | InReviewBySam | Under review |
| 210 | AwaitingApplicationReview | Under review |
| 215 | AwaitingAudit | Under review |
| 220 | InAudit | Under review |
| 225 | AuditorFollowup | Under review |
| 230 | ApplicantFollowUpRequestedByAuditor | Back with the applicant |
| 235 | ApplicantFollowUpRequestedBySam | Back with the applicant |
| 240 | AwaitingReview | Under review |
| 250 | AuditInReview | Under review |
| 310 | AwaitingDecision | Under review |
| 350 | SubmittedForFinalization | Deciding |
| 375 | Finalized | Certified |
| 400 | Rejected | Closed without certification |
| 500 | Cancelled | Closed |
Which of these your scheme actually uses depends on how your templates are set up — most integrations care about a handful: the applicant finishes (AwaitingApplicationReview), you send it back for more (ApplicantFollowUpRequestedByAuditor), and you close it (Finalized or Rejected).
1. Hear about changes
Subscribe to the two application topics and BeCause pushes every movement to you — Webhooks documents the payloads, signature and retry behavior. application.status_changed fires on every status transition; application.answers_changed fires when the applicant's answers change, batched so an applicant's autosave session becomes a delivery or two rather than a stream.
Two things to design for from the start:
- The payload is a pointer, not the data. It names the application and, for status events, the status reached; it never carries answers. On receipt, read the application or its answers below — that response is the truth.
- You hear your own writes echoed back. Setting a status or writing answers through this API publishes the same events to your subscription as anyone else's changes would. Make your receiver idempotent against its own actions rather than assuming every event is news.
Polling works too — GET /api/v1/applications with a status filter is a perfectly good hourly reconcile — but webhooks remove the asking.
2. List and read applications
curl "https://api.because.eco/api/v1/applications?status=210&page=1&limit=50" \
-H "Authorization: YOUR_API_KEY"
GET /api/v1/applications pages through your applications, optionally filtered to one status. List rows carry the ids, statuses and reporting period — never the applicant's details. For those, read one application:
curl "https://api.because.eco/api/v1/applications/98e3c390-19eb-484c-a9d1-d4f8b4af4839" \
-H "Authorization: YOUR_API_KEY"
GET /api/v1/applications/{applicationId} adds the applicantCompany block — name, address, contact details, and becauseProfileId, the company's public identifier across the whole partner API. Two null cases matter:
applicantCompanyisnull— nobody has claimed the application yet. There is no company to talk about, and no answers to read or write either.becauseProfileIdisnullinside a present block — the applicant's profile could not be resolved (deleted or merged away). Treat the company as not addressable: anything that matches on the identifier, such as a bulk upsert, would only create a duplicate.
An application that does not exist and an application owned by another certifier are indistinguishable: both are 404. That is deliberate — the API never confirms a foreign application's existence.
3. Read the answers
curl "https://api.because.eco/api/v1/applications/98e3c390-19eb-484c-a9d1-d4f8b4af4839/answers" \
-H "Authorization: YOUR_API_KEY"
GET /api/v1/applications/{applicationId}/answers returns the answer set and one flag that changes what it means:
isLockedSnapshot: false— the applicant has not submitted; you are reading their live working set, which can change under you.isLockedSnapshot: true— the applicant submitted; this is the locked snapshot under review, the set your verdict attests to.
Each answer carries the data point id, the reporting period, one typed value (textAnswer, numberAnswer, booleanAnswer, the date fields, or listAnswer), file URLs where documents were uploaded, and updatedDateUtc. Three kinds of answers never appear, by design: drafts the applicant has not committed, formula-computed values BeCause derives, and table answers — the set is what a reviewer attests to, scalar values only.
4. Write your decisions back
Both writes accept an optional actingUserId naming who acted in your system; omitted, the change is attributed to the API key itself. Send it when you have it — the applicant sees the audit trail.
Status. PUT /api/v1/applications/{applicationId}/status moves the application:
curl -X PUT "https://api.because.eco/api/v1/applications/98e3c390-19eb-484c-a9d1-d4f8b4af4839/status" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": 375, "actingUserId": "auditor-42" }'
Setting the status an application already has changes nothing and emits no events, so a retried request is harmless — write the target status without reading first.
Answers. PUT /api/v1/applications/{applicationId}/answers writes answers onto the applicant — audit corrections, values your auditor established on site, scores your side computes:
curl -X PUT "https://api.because.eco/api/v1/applications/98e3c390-19eb-484c-a9d1-d4f8b4af4839/answers" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"actingUserId": "auditor-42",
"answers": [
{
"dataPointId": "f86aacc7-2eee-4bd0-b2cb-cde5f5ecf169",
"year": 2026,
"month": 6,
"reportingPeriodType": 2,
"numberAnswer": 128400.5
}
]
}'
The rules that shape this endpoint:
- Every answer lands as verified. Attesting to a value through this contract is, by definition, verifying it — there is no draft state on this surface.
- Every data point must belong to the application's template. One unknown data point rejects the whole batch with 400, and nothing is partially applied.
- The application must be claimed. Until an applicant exists there is nobody to attribute answers to; the write fails with 400.
- Values are typed —
numberAnsweris a number,booleanAnswera boolean — and each answer carries exactly one value member.
Traps worth knowing
- Do not count
application.answers_changedevents. They are batched while a delivery is pending, so five autosaves may arrive as one event. The event means "the answers are different now — re-read them", never "one answer changed". - Your webhook receiver hears you. A naive "status changed → push it to BeCause" loop feeds on its own echo. Guard writes with "only if different" — the status endpoint's same-status no-op helps, but the guard belongs in your receiver.
- A
becauseProfileIdofnullis not an error to retry. No retry produces an identifier the profile does not carry; route it to a human instead.
Building something this doesn't cover? Tell support@because.eco what you are integrating.