Version 1.0.0
Droomwork PROOF ACADEMIC
Credential verification and competence claims for Nigeria.
https://sandbox.droomwork.ioDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…PROOF tells you what someone can do, how confident that answer is, and how old it is.
What this API gives you, and what it will not
You get assertions, never raw evidence. Each claim carries its level, its evidence class, the date the evidence was gathered and how it was gathered. You don't get the transcript, the certificate image or the internals of any score. Those are the subject's, not yours.
Three outcomes, and they aren't interchangeable. verified means the issuer confirmed it. contradicted means the issuer said otherwise. unverified means nobody answered. The second is a finding about the person and the third is a finding about the register. Treat them as one failure and you've thrown away the difference that matters.
A register that doesn't answer is never a pass. You get unverified with the source named. Silence is not evidence.
Competence decays, and you can read the decay. Every claim carries an expiry from the moment it's issued, ages toward it on a function you can read, and can't be extended without it showing. A lapsed claim is shown as lapsed and downgraded, never deleted. The history is the point.
A statutory licence is a gate, not a score. Where the law requires a licence for an occupation, its absence blocks. No other evidence substitutes for it, and you can't weigh it against anything here.
Verify once, reuse with consent. A Wallet entry another organisation already paid for is reused as a consented disclosure. You aren't charged to verify it again.
Getting started
Get the subject a live, unrevoked ANCHOR Passport. Submit a credential. Read the Skill Wallet.
Every operation below has two samples: Direct HTTP, with nothing but your language's own client, and Client library, with ours. Same request, same answer; the switch above each sample picks one and remembers it. Which should I use?
/v1/credentials/verifications#See which credentials you have checked
credentials.verifications.list
A verification is one question put to one issuer about one qualification: does this person hold what they say they hold. This lists the ones your organisation has opened, and each row carries its own outcome, so one screen in your product can show where several checks stand without a call for each.
You get a page of verifications. Each carries the id you read it back with, plus the subject_ref, issuer_id and credential_kind you sent when you opened the check.
Read one in full at GET /v1/credentials/verifications/{verification_id}.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
outcome
string
optionalRestricts the page to one outcome: pending (an assisted path is still running), verified (the issuer confirmed it), unverified (nobody answered) or contradicted (the issuer said otherwise). Leave it out to get every outcome.
pendingverifiedunverifiedcontradictedsubject_ref
string
optionalRestricts the page to one person: the opaque subject_ref you chose and sent when you verified their credential at POST /v1/credentials/verifications. Leave it out to get every subject.
Returns
The claimed_award and claimed_year you sent never come back on a row.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Verification
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
19 fields of Verification
id
string
requiredThe verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.
object
always "credential_verification"
requiredAlways credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
outcome
string
requiredFour values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.
pendingverifiedunverifiedcontradictedsubject_ref
string
requiredThe opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.
passport_id
string
optionalThe live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.
identity_assurance
string
optionalHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedissuer_id
string
requiredThe issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.
issuer_name
string
optionalThe issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.
method
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualcredential_kind
string
optionalWhat kind of credential was checked, as you sent it, such as bachelor_degree.
evidence_date
string · date · nullable
optionalWhen the evidence was gathered, not when it was read.
unresolved_source
string · nullable
optionalNamed when the outcome is unverified. Silence always names its source.
contradiction_detail
string · nullable
optionalWhat the issuer actually said, where it disagreed.
service_level_due
string · date · nullable
optionalPresent while pending. When the assisted path is due to answer.
claim_id
string · nullable
optionalThe competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.
cached
boolean
optionalTrue when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
const result = await api.credentialsVerificationsList({ limit: 25, outcome: 'pending', subjectRef: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
result = api.credentials_verifications_list(limit=25, outcome='pending', subject_ref='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
$result = $api->credentialsVerificationsList(limit: 25, outcome: 'pending', subject_ref: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
var result = api.credentialsVerificationsList(25, null, ProofVerificationOutcome.fromValue("pending"), "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
var result = api.CredentialsVerificationsList(limit: 25, outcome: ProofVerificationOutcome.Pending, subjectRef: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
result, _, err := client.PROOFAPI.CredentialsVerificationsList(ctx).Limit(25).Outcome(droomwork.ProofVerificationOutcome("pending")).SubjectRef("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: limit (optional), starting_after (optional), outcome (optional), subject_ref (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/verifications?limit=25&outcome=pending&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_verification",
"livemode": false,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "example",
"credential_kind": "bachelor_degree",
"evidence_date": "2026-09-01",
"unresolved_source": "example",
"contradiction_detail": "example",
"service_level_due": "2026-09-01",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"cached": true,
"created_at": "2026-09-01T09:00:00Z"
}
],
"has_more": true
}
/v1/credentials/verifications#Verify a credential
credentials.verifications.create
This opens a check on one claimed qualification. One call is one credential, so a degree and a youth service certificate from the same person are two checks, and you can tell later which of the two stood up.
Send your own reference for the person, the issuer and the kind of credential, taking the issuer_id from GET /v1/credentials/issuers. You get the verification back, with an id starting proof_academic_verification_ to keep and the outcome the check has reached.
credential_kind is a free string with no list to choose from. Settle on one spelling for each kind of credential before you start.
A pending outcome is not a failure and not a pass.
Carry the id forward as verification_id: GET /v1/credentials/verifications/{verification_id} reads it back, and each of the three outcome calls names it in the path.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
subject_ref
string
requiredAn opaque reference you choose for this person, not a name and not an identifier. Send the same subject_ref on every call about them, POST /v1/credentials/wallets included, so their Wallet and claims line up.
passport_id
string
optionalThe subject's live, unrevoked ANCHOR Passport: the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Omit it when your organisation supplies identity under an attestation.
issuer_id
string
requiredThe issuer that granted the credential: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. Its method tells you whether to expect pending.
credential_kind
string
requiredWhat kind of credential you are checking, such as bachelor_degree. It comes back unchanged on the verification.
claimed_award
string
optionalThe award the subject says they hold, such as the degree or certificate title. It's what the issuer is asked to confirm; omit it if you don't have it.
claimed_year
integer
optionalThe year the subject says the credential was awarded, as a whole number. Omit it if you don't have it.
document_analysis_id
string
optionalLinks an earlier document analysis to this verification: the id returned by POST /v1/credentials/document_analyses, starting proof_academic_verification_. Leave it out if you haven't analysed the document.
Returns
The check as recorded, saying whether existing evidence was reused instead of asking the issuer again.
id
string
requiredThe verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.
object
always "credential_verification"
requiredAlways credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
outcome
string
requiredFour values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.
pendingverifiedunverifiedcontradictedsubject_ref
string
requiredThe opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.
passport_id
string
optionalThe live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.
identity_assurance
string
optionalHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedissuer_id
string
requiredThe issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.
issuer_name
string
optionalThe issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.
method
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualcredential_kind
string
optionalWhat kind of credential was checked, as you sent it, such as bachelor_degree.
evidence_date
string · date · nullable
optionalWhen the evidence was gathered, not when it was read.
unresolved_source
string · nullable
optionalNamed when the outcome is unverified. Silence always names its source.
contradiction_detail
string · nullable
optionalWhat the issuer actually said, where it disagreed.
service_level_due
string · date · nullable
optionalPresent while pending. When the assisted path is due to answer.
claim_id
string · nullable
optionalThe competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.
cached
boolean
optionalTrue when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/verifications" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","issuer_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","credential_kind":"bachelor_degree","passport_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","claimed_award":"example","claimed_year":1,"document_analysis_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsVerificationsCreate({
idempotencyKey: crypto.randomUUID(),
proofVerificationCreateRequest: {"subjectRef":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","issuerId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","credentialKind":"bachelor_degree","passportId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","claimedAward":"example","claimedYear":1,"documentAnalysisId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/verifications', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"credential_kind": "bachelor_degree",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claimed_award": "example",
"claimed_year": 1,
"document_analysis_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_verifications_create(body={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "credential_kind": "bachelor_degree", "passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "claimed_award": "example", "claimed_year": 1, "document_analysis_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/verifications',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "credential_kind": "bachelor_degree", "passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "claimed_award": "example", "claimed_year": 1, "document_analysis_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsVerificationsCreate($idempotencyKey, json_decode('{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","issuer_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","credential_kind":"bachelor_degree","passport_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","claimed_award":"example","claimed_year":1,"document_analysis_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/verifications');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","issuer_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","credential_kind":"bachelor_degree","passport_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","claimed_award":"example","claimed_year":1,"document_analysis_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsVerificationsCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/verifications"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"credential_kind": "bachelor_degree",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claimed_award": "example",
"claimed_year": 1,
"document_analysis_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsVerificationsCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/verifications");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"credential_kind": "bachelor_degree",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claimed_award": "example",
"claimed_year": 1,
"document_analysis_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsVerificationsCreate(ctx).IdempotencyKey(key).ProofVerificationCreateRequest(body).Execute()body := strings.NewReader(`{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"credential_kind": "bachelor_degree",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claimed_award": "example",
"claimed_year": 1,
"document_analysis_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/verifications", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_verification",
"livemode": false,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "example",
"credential_kind": "bachelor_degree",
"evidence_date": "2026-09-01",
"unresolved_source": "example",
"contradiction_detail": "example",
"service_level_due": "2026-09-01",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"cached": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/verifications/{verification_id}#Where one credential check stands
credentials.verifications.retrieve
This tells you what became of one credential check, which is one question put to one issuer about one qualification. The identifier is all this call needs, so your product can keep the id and nothing else and still read the outcome at the moment it matters.
Send the verification's id in the path. You get the whole record: the outcome it has reached, and the subject_ref, issuer_id and credential_kind you sent when you opened the check.
An id that names no verification answers not_found.
Path parameters
verification_id
string
requiredThe verification's identifier: the id returned by POST /v1/credentials/verifications or listed by GET /v1/credentials/verifications. It starts with proof_academic_verification_.
Returns
The check in full, with what the issuer actually said, so a disagreement can be put to the person.
id
string
requiredThe verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.
object
always "credential_verification"
requiredAlways credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
outcome
string
requiredFour values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.
pendingverifiedunverifiedcontradictedsubject_ref
string
requiredThe opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.
passport_id
string
optionalThe live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.
identity_assurance
string
optionalHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedissuer_id
string
requiredThe issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.
issuer_name
string
optionalThe issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.
method
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualcredential_kind
string
optionalWhat kind of credential was checked, as you sent it, such as bachelor_degree.
evidence_date
string · date · nullable
optionalWhen the evidence was gathered, not when it was read.
unresolved_source
string · nullable
optionalNamed when the outcome is unverified. Silence always names its source.
contradiction_detail
string · nullable
optionalWhat the issuer actually said, where it disagreed.
service_level_due
string · date · nullable
optionalPresent while pending. When the assisted path is due to answer.
claim_id
string · nullable
optionalThe competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.
cached
boolean
optionalTrue when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsVerificationsRetrieve({ verificationId: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_verifications_retrieve(verification_id='proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsVerificationsRetrieve(verification_id: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsVerificationsRetrieve("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsVerificationsRetrieve(verificationId: "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsVerificationsRetrieve(ctx, "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_verification",
"livemode": false,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "example",
"credential_kind": "bachelor_degree",
"evidence_date": "2026-09-01",
"unresolved_source": "example",
"contradiction_detail": "example",
"service_level_due": "2026-09-01",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"cached": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/issuers#Choose the issuer that granted the credential
credentials.issuers.list
An issuer is the body that granted a credential and can be asked to confirm it. A credential check names one of them, so the set your product offers is data you read here and not a list you keep yourself.
You get the issuers, each with its name for showing to a reader, kind for what sort of body it is, and method for how it is reached. Keep the id of the one you want.
Read reachable before you settle on an issuer: being listed here and being reachable now are two different facts.
Carry the id forward as issuer_id at POST /v1/credentials/verifications, which is the call that checks a credential.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
kind
string
optionalRestricts the page to one kind of issuer: university, polytechnic, national_youth_service, professional_body, regulatory_body or examinations_board. Leave it out to get every kind.
universitypolytechnicnational_youth_serviceprofessional_bodyregulatory_bodyexaminations_boardmethod
string
optionalRestricts the page to issuers reached one way: register_api or issuer_portal, which answer by machine, or assisted_manual, which answers pending first to a published service level. Leave it out to get every issuer.
register_apiissuer_portalassisted_manualReturns
One row for each issuer, enough to choose a source that will actually answer you.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Issuer
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
8 fields of Issuer
id
string
requiredThe issuer's identifier. It starts with proof_academic_verification_, never changes, and is listed by GET /v1/credentials/issuers; send it as issuer_id when you verify a credential at POST /v1/credentials/verifications.
object
always "issuer"
requiredAlways issuer. Tells you which kind of record you are looking at, so one handler can read any response.
name
string
requiredThe issuer's name in words, for showing to a reader, such as Rivers State Internal Revenue Service. Name the issuer on calls by its id, never by this.
kind
string
requireduniversitypolytechnicnational_youth_serviceprofessional_bodyregulatory_bodyexaminations_boardmethod
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualservice_level_days
integer · nullable
optionalPublished for an assisted path. How long pending should last.
evidence_validity_days
integer
optionalHow long evidence from this issuer stays usable before re-querying.
reachable
boolean
optionalWhether this issuer can currently be reached by its method. Read it before you verify against it: a source that can't answer gives you unverified, never a pass.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
const result = await api.credentialsIssuersList({ limit: 25, kind: 'university', method: 'register_api' });// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
result = api.credentials_issuers_list(limit=25, kind='university', method='register_api')import os
import requests
# query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
$result = $api->credentialsIssuersList(limit: 25, kind: 'university', method: 'register_api');<?php
// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
var result = api.credentialsIssuersList(25, null, ProofIssuerKind.fromValue("university"), ProofVerificationMethod.fromValue("register_api"));// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
var result = api.CredentialsIssuersList(limit: 25, kind: ProofIssuerKind.University, method: ProofVerificationMethod.RegisterApi);// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
result, _, err := client.PROOFAPI.CredentialsIssuersList(ctx).Limit(25).Kind(droomwork.ProofIssuerKind("university")).Method(droomwork.ProofVerificationMethod("register_api")).Execute()// query parameters: limit (optional), starting_after (optional), kind (optional), method (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/issuers?limit=25&kind=university&method=register_api", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "issuer",
"name": "example",
"kind": "university",
"method": "register_api",
"service_level_days": 1,
"evidence_validity_days": 1,
"reachable": true
}
],
"has_more": true
}
/v1/credentials/issuers/{issuer_id}#Check one issuer before you verify
credentials.issuers.retrieve
An issuer is the body that granted a credential and can be asked to confirm it, such as a university or a regulatory body. This is the one you are about to name on a check, so you can see what you are relying on first.
You get the issuer: its name in words, what sort of body it is, how it is reached, whether it can be reached at the moment, and how long evidence from it stays usable before the register has to be asked again.
Send this id as issuer_id at POST /v1/credentials/verifications to check a credential this issuer granted.
Path parameters
issuer_id
string
requiredThe issuer's identifier: the id of an issuer listed by GET /v1/credentials/issuers, starting proof_academic_verification_. The same value you send as issuer_id when you verify a credential.
Returns
One issuer in full, with how long an assisted answer should take, so a wait can be planned.
id
string
requiredThe issuer's identifier. It starts with proof_academic_verification_, never changes, and is listed by GET /v1/credentials/issuers; send it as issuer_id when you verify a credential at POST /v1/credentials/verifications.
object
always "issuer"
requiredAlways issuer. Tells you which kind of record you are looking at, so one handler can read any response.
name
string
requiredThe issuer's name in words, for showing to a reader, such as Rivers State Internal Revenue Service. Name the issuer on calls by its id, never by this.
kind
string
requireduniversitypolytechnicnational_youth_serviceprofessional_bodyregulatory_bodyexaminations_boardmethod
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualservice_level_days
integer · nullable
optionalPublished for an assisted path. How long pending should last.
evidence_validity_days
integer
optionalHow long evidence from this issuer stays usable before re-querying.
reachable
boolean
optionalWhether this issuer can currently be reached by its method. Read it before you verify against it: a source that can't answer gives you unverified, never a pass.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsIssuersRetrieve({ issuerId: '{issuer_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_issuers_retrieve(issuer_id='{issuer_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsIssuersRetrieve(issuer_id: '{issuer_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsIssuersRetrieve("{issuer_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsIssuersRetrieve(issuerId: "{issuer_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsIssuersRetrieve(ctx, "{issuer_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/issuers/%7Bissuer_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "issuer",
"name": "example",
"kind": "university",
"method": "register_api",
"service_level_days": 1,
"evidence_validity_days": 1,
"reachable": true
}
/v1/credentials/frameworks#Which occupations are supported, and their claims
credentials.frameworks.list
This is the set of occupations Droomwork supports, each broken into the separate competences a person can be assessed on. Because they are assessed one at a time, you can decide which ones a piece of work actually needs and which it does not.
You get the frameworks, each with its occupation code, the occupation in words, its version, nvqf_level for its mapped level on the national vocational qualification framework, and the claims it breaks into.
Frameworks are versioned, and a claim keeps the meaning it had under its own version, whatever a later one says. Read a claim's framework_version before you measure it against what you read here.
Keep the id to come back to one framework on its own at GET /v1/credentials/frameworks/{framework_id}, passing it as framework_id.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
occupation_code
string
optionalRestricts the page to one occupation code, such as ng-7412: the occupation_code a framework carries, and the code a Wallet's statutory_blocks name. Leave it out to get every supported occupation.
Returns
One framework for each occupation supported, carrying the codes your own records must match on.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Framework
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
7 fields of Framework
id
string
requiredThe framework's identifier. It starts with proof_academic_verification_, never changes, and is listed by GET /v1/credentials/frameworks; send it as framework_id to GET /v1/credentials/frameworks/{framework_id}.
object
always "competency_framework"
requiredAlways competency_framework. Tells you which kind of record you are looking at, so one handler can read any response.
occupation_code
string
requiredThe occupation this framework breaks down, as its code, such as ng-7412. Pass it as occupation_code to filter frameworks and claims.
occupation_name
string
optionalThe occupation in words, for showing beside the code. Match on occupation_code, not on this.
version
string
requiredWhich version of the framework this is, such as 2026.08.1. A claim carries the version it was made under and keeps meaning what it meant then.
nvqf_level
integer · nullable
optionalThe mapped level on the national vocational qualification framework.
claims
array of object
requiredIndependently assessable. Someone can hold three of five.
3 fields
code
string
requiredThe claim's code within this framework. It's the claim_code a competence claim carries, so match on it rather than on description.
description
string
requiredWhat the claim says the person can do, in words. Show it; match on code.
nvqf_level
integer · nullable
optionalThe level this claim maps to on the national vocational qualification framework. null where the claim has no mapped level.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), occupation_code (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), occupation_code (optional)
const result = await api.credentialsFrameworksList({ limit: 25, occupationCode: 'ng-7412' });// query parameters: limit (optional), occupation_code (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), occupation_code (optional)
result = api.credentials_frameworks_list(limit=25, occupation_code='ng-7412')import os
import requests
# query parameters: limit (optional), occupation_code (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), occupation_code (optional)
$result = $api->credentialsFrameworksList(limit: 25, occupation_code: 'ng-7412');<?php
// query parameters: limit (optional), occupation_code (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), occupation_code (optional)
var result = api.credentialsFrameworksList(25, "ng-7412");// query parameters: limit (optional), occupation_code (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), occupation_code (optional)
var result = api.CredentialsFrameworksList(limit: 25, occupationCode: "ng-7412");// query parameters: limit (optional), occupation_code (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), occupation_code (optional)
result, _, err := client.PROOFAPI.CredentialsFrameworksList(ctx).Limit(25).OccupationCode("ng-7412").Execute()// query parameters: limit (optional), occupation_code (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/frameworks?limit=25&occupation_code=ng-7412", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competency_framework",
"occupation_code": "ng-7412",
"version": "2026.08.1",
"claims": [
{
"code": "invalid_money_amount",
"description": "example",
"nvqf_level": 1
}
],
"occupation_name": "example",
"nvqf_level": 1
}
],
"has_more": true
}
/v1/credentials/frameworks/{framework_id}#What one occupation breaks down into
credentials.frameworks.retrieve
A competency framework is one occupation written down as the separate competences it is made of, each one assessable by itself. So you can put an occupation in front of a reader as competences they can read, and keep your own matching on the codes.
You get the framework: the occupation code, the occupation in words, its version, its mapped level on the national vocational qualification framework, and the claims, each with its code and what it says a person can do.
Path parameters
framework_id
string
requiredThe framework's identifier: the id of a framework listed by GET /v1/credentials/frameworks, starting proof_academic_verification_. Filter that list by occupation_code to find the one you want.
Returns
One occupation with its competences, which is where a stored claim's meaning comes from.
id
string
requiredThe framework's identifier. It starts with proof_academic_verification_, never changes, and is listed by GET /v1/credentials/frameworks; send it as framework_id to GET /v1/credentials/frameworks/{framework_id}.
object
always "competency_framework"
requiredAlways competency_framework. Tells you which kind of record you are looking at, so one handler can read any response.
occupation_code
string
requiredThe occupation this framework breaks down, as its code, such as ng-7412. Pass it as occupation_code to filter frameworks and claims.
occupation_name
string
optionalThe occupation in words, for showing beside the code. Match on occupation_code, not on this.
version
string
requiredWhich version of the framework this is, such as 2026.08.1. A claim carries the version it was made under and keeps meaning what it meant then.
nvqf_level
integer · nullable
optionalThe mapped level on the national vocational qualification framework.
claims
array of object
requiredIndependently assessable. Someone can hold three of five.
3 fields
code
string
requiredThe claim's code within this framework. It's the claim_code a competence claim carries, so match on it rather than on description.
description
string
requiredWhat the claim says the person can do, in words. Show it; match on code.
nvqf_level
integer · nullable
optionalThe level this claim maps to on the national vocational qualification framework. null where the claim has no mapped level.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsFrameworksRetrieve({ frameworkId: '{framework_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_frameworks_retrieve(framework_id='{framework_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsFrameworksRetrieve(framework_id: '{framework_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsFrameworksRetrieve("{framework_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsFrameworksRetrieve(frameworkId: "{framework_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsFrameworksRetrieve(ctx, "{framework_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/frameworks/%7Bframework_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competency_framework",
"occupation_code": "ng-7412",
"version": "2026.08.1",
"claims": [
{
"code": "invalid_money_amount",
"description": "example",
"nvqf_level": 1
}
],
"occupation_name": "example",
"nvqf_level": 1
}
/v1/credentials/document_analyses#Analyse a document for signs of forgery
credentials.document_analyses.create
This checks whether a document somebody handed you is the credential it claims to be. So your product gets a finding read off the document itself, with no issuer having to answer.
Send the document's URL, its credential_kind and a subject_ref for the person it names. You get the analysis back, with an id starting proof_academic_verification_, the overall finding, and a score with a note for each factor the document was read against.
Nothing has to exist first. All three fields are values you already hold, not identifiers another call returned.
Keep the id. You read the analysis back with it at GET /v1/credentials/document_analyses/{document_analysis_id}, and an appeal names it as against.id at POST /v1/credentials/appeals.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
subject_ref
string
requiredAn opaque reference you choose for the person the document names; not a name and not an identifier. Send the same subject_ref you send at POST /v1/credentials/verifications so you can link this analysis to a verification.
document_url
string · uri · write only
requiredWrite only. The document is analysed and never returned, because a certificate belongs to the person it names.
credential_kind
string
requiredWhat kind of credential the document claims to be, such as bachelor_degree. Send the same value you'd use to verify it.
Returns
The examination of one document, in enough detail to say which factor raised a concern.
id
string
requiredThe analysis's identifier. It starts with proof_academic_verification_, never changes, and is returned by POST /v1/credentials/document_analyses; send it as document_analysis_id at POST /v1/credentials/verifications.
object
always "document_analysis"
requiredAlways document_analysis. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
optionalThe opaque reference to the person the document names, exactly as you chose and sent it in subject_ref at POST /v1/credentials/document_analyses. Not a name and not an identifier.
finding
string
requiredThe overall finding: consistent with a genuine document, questionable, or confirmed_forgery. Only confirmed_forgery is escalated to ANCHOR, shown by escalated_to_anchor; read factors to see which score raised a concern.
consistentquestionableconfirmed_forgeryfactors
array of object
requiredOne score per factor, never a single number. A combined figure hides which factor raised the concern and cannot be argued with.
3 fields
factor
string
requiredFive factors, scored separately so a concern can be pointed at.
templatesealtypographyserial_structureissuance_plausibilityscore
number · minimum 0 · maximum 1
requiredHigher means more consistent with a genuine document.
note
string
requiredA remark on this factor, in words, so you can say what raised a concern. It isn't the reasoning behind the score; you don't get that.
escalated_to_anchor
boolean
optionalTrue on a confirmed forgery, which is escalated to ANCHOR to decide whether to blocklist.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/document_analyses" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","document_url":"https://files.sandbox.droomwork.com/example","credential_kind":"bachelor_degree"}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsDocumentAnalysesCreate({
idempotencyKey: crypto.randomUUID(),
proofDocumentAnalysisCreateRequest: {"subjectRef":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","documentUrl":"https://files.sandbox.droomwork.com/example","credentialKind":"bachelor_degree"},
});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/document_analyses', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"document_url": "https://files.sandbox.droomwork.com/example",
"credential_kind": "bachelor_degree"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_document_analyses_create(body={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "document_url": "https://files.sandbox.droomwork.com/example", "credential_kind": "bachelor_degree"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/document_analyses',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "document_url": "https://files.sandbox.droomwork.com/example", "credential_kind": "bachelor_degree"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsDocumentAnalysesCreate($idempotencyKey, json_decode('{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","document_url":"https://files.sandbox.droomwork.com/example","credential_kind":"bachelor_degree"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/document_analyses');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","document_url":"https://files.sandbox.droomwork.com/example","credential_kind":"bachelor_degree"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsDocumentAnalysesCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/document_analyses"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"document_url": "https://files.sandbox.droomwork.com/example",
"credential_kind": "bachelor_degree"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsDocumentAnalysesCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/document_analyses");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"document_url": "https://files.sandbox.droomwork.com/example",
"credential_kind": "bachelor_degree"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsDocumentAnalysesCreate(ctx).IdempotencyKey(key).ProofDocumentAnalysisCreateRequest(body).Execute()body := strings.NewReader(`{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"document_url": "https://files.sandbox.droomwork.com/example",
"credential_kind": "bachelor_degree"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/document_analyses", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "document_analysis",
"livemode": false,
"mocked": true,
"finding": "consistent",
"factors": [
{
"factor": "template",
"score": 0,
"note": "example"
}
],
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"escalated_to_anchor": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/document_analyses/{document_analysis_id}#What the document forensics found
credentials.document_analyses.retrieve
This reads back one examination of one document for signs of forgery, as it was recorded. So your own system can keep the id alone and read the finding again each time it acts on that document.
Send the analysis's id in the path. You get the record as it was written: the overall finding, and a score with a note for each factor the document was read against.
An identifier with nothing behind it answers not_found. The document itself is not part of the record and never comes back here.
Path parameters
document_analysis_id
string
requiredThe analysis's identifier: the id returned by POST /v1/credentials/document_analyses, starting proof_academic_verification_.
Returns
Nothing here carries the credential_kind you sent when you asked for the analysis.
id
string
requiredThe analysis's identifier. It starts with proof_academic_verification_, never changes, and is returned by POST /v1/credentials/document_analyses; send it as document_analysis_id at POST /v1/credentials/verifications.
object
always "document_analysis"
requiredAlways document_analysis. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
optionalThe opaque reference to the person the document names, exactly as you chose and sent it in subject_ref at POST /v1/credentials/document_analyses. Not a name and not an identifier.
finding
string
requiredThe overall finding: consistent with a genuine document, questionable, or confirmed_forgery. Only confirmed_forgery is escalated to ANCHOR, shown by escalated_to_anchor; read factors to see which score raised a concern.
consistentquestionableconfirmed_forgeryfactors
array of object
requiredOne score per factor, never a single number. A combined figure hides which factor raised the concern and cannot be argued with.
3 fields
factor
string
requiredFive factors, scored separately so a concern can be pointed at.
templatesealtypographyserial_structureissuance_plausibilityscore
number · minimum 0 · maximum 1
requiredHigher means more consistent with a genuine document.
note
string
requiredA remark on this factor, in words, so you can say what raised a concern. It isn't the reasoning behind the score; you don't get that.
escalated_to_anchor
boolean
optionalTrue on a confirmed forgery, which is escalated to ANCHOR to decide whether to blocklist.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsDocumentAnalysesRetrieve({ documentAnalysisId: '{document_analysis_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_document_analyses_retrieve(document_analysis_id='{document_analysis_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsDocumentAnalysesRetrieve(document_analysis_id: '{document_analysis_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsDocumentAnalysesRetrieve("{document_analysis_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsDocumentAnalysesRetrieve(documentAnalysisId: "{document_analysis_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsDocumentAnalysesRetrieve(ctx, "{document_analysis_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/document_analyses/%7Bdocument_analysis_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "document_analysis",
"livemode": false,
"mocked": true,
"finding": "consistent",
"factors": [
{
"factor": "template",
"score": 0,
"note": "example"
}
],
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"escalated_to_anchor": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/wallets#Find a Skill Wallet to read
credentials.wallets.list
A Skill Wallet is one person's competence claims and the licences they hold, kept in one record. This hands you a page of them, so a Wallet whose identifier your own system never stored can still be found.
Each Wallet on the page carries its id, starting proof_academic_wallet_, and the subject_ref you chose for the person it belongs to.
Read one in full at GET /v1/credentials/wallets/{wallet_id}, and carry the id forward as wallet_id to POST /v1/credentials/disclosures.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
subject_ref
string
optionalRestricts the page to one person, to see what's established before you pay to verify again: the opaque subject_ref you sent at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Leave it out to get every Wallet.
Returns
One whole Wallet for each person who matched, so a decision needs no second read.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of SkillWallet
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
8 fields of SkillWallet
id
string
requiredThe Wallet's identifier. It starts with proof_academic_wallet_, never changes, is returned by POST /v1/credentials/wallets and listed by GET /v1/credentials/wallets; send it as wallet_id at POST /v1/credentials/disclosures.
object
always "skill_wallet"
requiredAlways skill_wallet. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this Wallet belongs to, as you sent it at POST /v1/credentials/wallets or POST /v1/credentials/verifications; every claim and licence inside carries the same one. Not a name and not an identifier.
claims
array of CompetenceClaim
requiredLapsed claims appear, marked lapsed. Nothing is removed.
16 fields of CompetenceClaim
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
licences
array of Licence
optionalThe statutory and professional licences this person holds, each with its status and whether the law requires it. Read statutory_block on each before you weigh anything else in the Wallet.
11 fields of Licence
id
string
requiredThis licence's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as licence_id to GET /v1/credentials/licences/{licence_id}.
object
always "licence"
requiredAlways licence. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe person who holds the licence, as the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
licence_kind
string
requiredWhat the licence is for, as a code such as electrical_installation_practitioner. Read statutorily_required_for for the occupations the law ties it to.
issuer_id
string
optionalThe body that granted the licence, as the id of an issuer from GET /v1/credentials/issuers, starting with proof_academic_verification_. Read it at GET /v1/credentials/issuers/{issuer_id} for its kind and how it's reached.
licence_number_masked
string
optionalMasked. The number itself belongs to the holder.
status
string
requiredvalidexpiringexpiredrevokedissued_on
string · date
optionalThe day the issuer granted the licence, as a YYYY-MM-DD date.
expires_on
string · date
optionalThe day the licence runs out, as a YYYY-MM-DD date. Once it passes, status is expired, the Wallet entry expires on its own, and you're told if you hold a live disclosure.
statutorily_required_for
array of string
optionalOccupations where the law requires this licence.
statutory_block
boolean
requiredTrue when this licence is legally required and is not currently valid. Read it as a gate on its own. It is not a factor to weigh against other evidence, and nothing else in the Wallet substitutes for it.
statutory_blocks
array of object
optionalOccupations this person may not lawfully work in, because a licence the law requires is absent or expired. A gate in its own right. No other evidence in this Wallet substitutes for one of these.
2 fields
occupation_code
string
requiredThe occupation this person may not lawfully work in, as an occupation code such as ng-7412: the same code that names its competency framework.
reason
string
requiredWhy the block stands: licence_absent when no licence the law requires is held, licence_expired when the one held has run out, licence_revoked when the issuer withdrew it. Any one blocks alone.
licence_absentlicence_expiredlicence_revokedhas_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), subject_ref (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
const result = await api.credentialsWalletsList({ limit: 25, subjectRef: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), starting_after (optional), subject_ref (optional)
result = api.credentials_wallets_list(limit=25, subject_ref='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
# query parameters: limit (optional), starting_after (optional), subject_ref (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), subject_ref (optional)
$result = $api->credentialsWalletsList(limit: 25, subject_ref: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
var result = api.credentialsWalletsList(25, null, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
var result = api.CredentialsWalletsList(limit: 25, subjectRef: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
result, _, err := client.PROOFAPI.CredentialsWalletsList(ctx).Limit(25).SubjectRef("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()// query parameters: limit (optional), starting_after (optional), subject_ref (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/wallets?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "skill_wallet",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claims": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"licences": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "licence",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_kind": "electrical_installation_practitioner",
"status": "valid",
"statutory_block": true,
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_number_masked": "example",
"issued_on": "2026-09-01",
"expires_on": "2026-09-01",
"statutorily_required_for": [
"example"
]
}
],
"statutory_blocks": [
{
"occupation_code": "ng-7412",
"reason": "licence_absent"
}
]
}
],
"has_more": true
}
/v1/credentials/wallets#Open a Wallet for one person
credentials.wallets.create
This opens a Wallet for one person. The identifier it hands back never changes, so your own system can keep it against that person and reach their Wallet again without looking it up.
Send one field in the body, the subject_ref you chose for that person. You get the Wallet back, with an id starting proof_academic_wallet_.
Nothing has to exist first. Use the subject_ref you already chose for this person.
Carry the id forward as wallet_id, to POST /v1/credentials/disclosures and to GET /v1/credentials/wallets/{wallet_id}.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body optional
subject_ref
string
optionalThe opaque reference you chose for this person, the same subject_ref you send at POST /v1/credentials/verifications, so their Wallet and claims line up. Not a name and not an identifier.
Returns
No field dates the Wallet itself, so note the day if you need it.
id
string
requiredThe Wallet's identifier. It starts with proof_academic_wallet_, never changes, is returned by POST /v1/credentials/wallets and listed by GET /v1/credentials/wallets; send it as wallet_id at POST /v1/credentials/disclosures.
object
always "skill_wallet"
requiredAlways skill_wallet. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this Wallet belongs to, as you sent it at POST /v1/credentials/wallets or POST /v1/credentials/verifications; every claim and licence inside carries the same one. Not a name and not an identifier.
claims
array of CompetenceClaim
requiredLapsed claims appear, marked lapsed. Nothing is removed.
16 fields of CompetenceClaim
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
licences
array of Licence
optionalThe statutory and professional licences this person holds, each with its status and whether the law requires it. Read statutory_block on each before you weigh anything else in the Wallet.
11 fields of Licence
id
string
requiredThis licence's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as licence_id to GET /v1/credentials/licences/{licence_id}.
object
always "licence"
requiredAlways licence. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe person who holds the licence, as the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
licence_kind
string
requiredWhat the licence is for, as a code such as electrical_installation_practitioner. Read statutorily_required_for for the occupations the law ties it to.
issuer_id
string
optionalThe body that granted the licence, as the id of an issuer from GET /v1/credentials/issuers, starting with proof_academic_verification_. Read it at GET /v1/credentials/issuers/{issuer_id} for its kind and how it's reached.
licence_number_masked
string
optionalMasked. The number itself belongs to the holder.
status
string
requiredvalidexpiringexpiredrevokedissued_on
string · date
optionalThe day the issuer granted the licence, as a YYYY-MM-DD date.
expires_on
string · date
optionalThe day the licence runs out, as a YYYY-MM-DD date. Once it passes, status is expired, the Wallet entry expires on its own, and you're told if you hold a live disclosure.
statutorily_required_for
array of string
optionalOccupations where the law requires this licence.
statutory_block
boolean
requiredTrue when this licence is legally required and is not currently valid. Read it as a gate on its own. It is not a factor to weigh against other evidence, and nothing else in the Wallet substitutes for it.
statutory_blocks
array of object
optionalOccupations this person may not lawfully work in, because a licence the law requires is absent or expired. A gate in its own right. No other evidence in this Wallet substitutes for one of these.
2 fields
occupation_code
string
requiredThe occupation this person may not lawfully work in, as an occupation code such as ng-7412: the same code that names its competency framework.
reason
string
requiredWhy the block stands: licence_absent when no licence the law requires is held, licence_expired when the one held has run out, licence_revoked when the issuer withdrew it. Any one blocks alone.
licence_absentlicence_expiredlicence_revokedOther responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/wallets" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsWalletsCreate({});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/wallets', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_wallets_create()import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/wallets',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsWalletsCreate();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/wallets');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsWalletsCreate();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/wallets"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsWalletsCreate();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/wallets");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsWalletsCreate(ctx).Execute()body := strings.NewReader(`{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/wallets", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "skill_wallet",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claims": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"licences": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "licence",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_kind": "electrical_installation_practitioner",
"status": "valid",
"statutory_block": true,
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_number_masked": "example",
"issued_on": "2026-09-01",
"expires_on": "2026-09-01",
"statutorily_required_for": [
"example"
]
}
],
"statutory_blocks": [
{
"occupation_code": "ng-7412",
"reason": "licence_absent"
}
]
}
/v1/credentials/wallets/{wallet_id}#See one person's claims and licences together
credentials.wallets.retrieve
This is the read a decision about one person comes off: their claims, the licences they hold and the occupations the law closes to them arrive in one answer, so nothing in your product has to line the three up itself.
Send the Wallet's id in the path. You get that Wallet back, with statutory_blocks naming any occupation closed to this person because a licence the law requires is not valid.
A claim in the Wallet is read in full at GET /v1/credentials/claims/{claim_id}, and an id you hold no Wallet for answers not_found.
Path parameters
wallet_id
string
requiredThe Wallet's identifier: the id of a Wallet from POST /v1/credentials/wallets or GET /v1/credentials/wallets. It starts with proof_academic_wallet_.
Returns
One person's whole record, with licence numbers masked, so you can act without storing them.
id
string
requiredThe Wallet's identifier. It starts with proof_academic_wallet_, never changes, is returned by POST /v1/credentials/wallets and listed by GET /v1/credentials/wallets; send it as wallet_id at POST /v1/credentials/disclosures.
object
always "skill_wallet"
requiredAlways skill_wallet. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this Wallet belongs to, as you sent it at POST /v1/credentials/wallets or POST /v1/credentials/verifications; every claim and licence inside carries the same one. Not a name and not an identifier.
claims
array of CompetenceClaim
requiredLapsed claims appear, marked lapsed. Nothing is removed.
16 fields of CompetenceClaim
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
licences
array of Licence
optionalThe statutory and professional licences this person holds, each with its status and whether the law requires it. Read statutory_block on each before you weigh anything else in the Wallet.
11 fields of Licence
id
string
requiredThis licence's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as licence_id to GET /v1/credentials/licences/{licence_id}.
object
always "licence"
requiredAlways licence. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe person who holds the licence, as the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
licence_kind
string
requiredWhat the licence is for, as a code such as electrical_installation_practitioner. Read statutorily_required_for for the occupations the law ties it to.
issuer_id
string
optionalThe body that granted the licence, as the id of an issuer from GET /v1/credentials/issuers, starting with proof_academic_verification_. Read it at GET /v1/credentials/issuers/{issuer_id} for its kind and how it's reached.
licence_number_masked
string
optionalMasked. The number itself belongs to the holder.
status
string
requiredvalidexpiringexpiredrevokedissued_on
string · date
optionalThe day the issuer granted the licence, as a YYYY-MM-DD date.
expires_on
string · date
optionalThe day the licence runs out, as a YYYY-MM-DD date. Once it passes, status is expired, the Wallet entry expires on its own, and you're told if you hold a live disclosure.
statutorily_required_for
array of string
optionalOccupations where the law requires this licence.
statutory_block
boolean
requiredTrue when this licence is legally required and is not currently valid. Read it as a gate on its own. It is not a factor to weigh against other evidence, and nothing else in the Wallet substitutes for it.
statutory_blocks
array of object
optionalOccupations this person may not lawfully work in, because a licence the law requires is absent or expired. A gate in its own right. No other evidence in this Wallet substitutes for one of these.
2 fields
occupation_code
string
requiredThe occupation this person may not lawfully work in, as an occupation code such as ng-7412: the same code that names its competency framework.
reason
string
requiredWhy the block stands: licence_absent when no licence the law requires is held, licence_expired when the one held has run out, licence_revoked when the issuer withdrew it. Any one blocks alone.
licence_absentlicence_expiredlicence_revokedOther responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsWalletsRetrieve({ walletId: 'proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_wallets_retrieve(wallet_id='proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsWalletsRetrieve(wallet_id: 'proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsWalletsRetrieve("proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsWalletsRetrieve(walletId: "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsWalletsRetrieve(ctx, "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/wallets/proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "skill_wallet",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claims": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"licences": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "licence",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_kind": "electrical_installation_practitioner",
"status": "valid",
"statutory_block": true,
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_number_masked": "example",
"issued_on": "2026-09-01",
"expires_on": "2026-09-01",
"statutorily_required_for": [
"example"
]
}
],
"statutory_blocks": [
{
"occupation_code": "ng-7412",
"reason": "licence_absent"
}
]
}
/v1/credentials/claims#Find a competence claim to act on
credentials.claims.list
A competence claim says one person can do one thing, and carries the class of evidence behind it and the day that evidence was gathered. This is the page of them, and it is where the id comes from for the calls that act on one claim.
Each claim on the page carries its id, starting proof_academic_claim_, its evidence_class for what the claim rests on, and its decay_state for whether the claim is still current.
Read one in full at GET /v1/credentials/claims/{claim_id}. The same id goes forward as claim_id, to POST /v1/credentials/reattestations to start a re-attestation, and in the path at POST /v1/credentials/claims/{claim_id}/lapse.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
subject_ref
string
optionalRestricts the page to claims about one person: the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Leave it out to get every subject you may see.
decay_state
string
optionalRestricts the page to claims in one decay state, in order of age: current, ageing, stale, or lapsed once expires_at has passed. Leave it out to get every state, lapsed included.
currentageingstalelapsedoccupation_code
string
optionalRestricts the page to claims made under the competency framework for one occupation code, such as ng-7412: the occupation_code of a framework from GET /v1/credentials/frameworks. Leave it out to get every occupation.
Returns
A page of claims, each with an expires_at you never sent, so you can re-check before that date.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of CompetenceClaim
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
16 fields of CompetenceClaim
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
const result = await api.credentialsClaimsList({ limit: 25, subjectRef: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', decayState: 'current', occupationCode: 'ng-7412' });// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
result = api.credentials_claims_list(limit=25, subject_ref='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', decay_state='current', occupation_code='ng-7412')import os
import requests
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
$result = $api->credentialsClaimsList(limit: 25, subject_ref: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', decay_state: 'current', occupation_code: 'ng-7412');<?php
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
var result = api.credentialsClaimsList(25, null, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", ProofDecayState.fromValue("current"), "ng-7412");// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
var result = api.CredentialsClaimsList(limit: 25, subjectRef: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", decayState: ProofDecayState.Current, occupationCode: "ng-7412");// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
result, _, err := client.PROOFAPI.CredentialsClaimsList(ctx).Limit(25).SubjectRef("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").DecayState(droomwork.ProofDecayState("current")).OccupationCode("ng-7412").Execute()// query parameters: limit (optional), starting_after (optional), subject_ref (optional), decay_state (optional), occupation_code (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/claims?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&decay_state=current&occupation_code=ng-7412", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"has_more": true
}
/v1/credentials/claims#Record a competence claim
credentials.claims.create
A claim normally appears when a verification comes back verified. This records one directly, for a competence established elsewhere, and the claim carries its own evidence class, so how far a later reader may rely on it is part of the record.
Nothing in the body is required. You get the claim back, with an id starting proof_academic_claim_ to keep, and an evidence_class.
Nothing has to exist first.
Carry the id forward as claim_id, to POST /v1/credentials/reattestations and to POST /v1/credentials/claims/{claim_id}/lapse.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body optional
verification_id
string
optionalThe verification the claim rests on, when there is one: the id of a verification from POST /v1/credentials/verifications, starting with proof_academic_verification_. Leave it out to record a competence established elsewhere.
Returns
The new claim, carrying an end date from the start, so nothing recorded here stands forever.
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/claims" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"verification_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsClaimsCreate({});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/claims', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_claims_create()import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/claims',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsClaimsCreate();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/claims');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"verification_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsClaimsCreate();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/claims"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsClaimsCreate();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/claims");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsClaimsCreate(ctx).Execute()body := strings.NewReader(`{
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/claims", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/credentials/claims/{claim_id}#See what one claim rests on
credentials.claims.retrieve
This reads one competence claim back as it stands now. A claim you stored earlier can have been lapsed since, so this is the call you come back to before you rely on it.
Send the claim's id in the path. You get that claim back, with evidence_class for what it rests on, evidence_date for the day the evidence was gathered, and decay_state for whether it is still current.
The classes are ordered by how directly the evidence observed the work, not by how formal it looks, so a demonstration outranks a certificate.
An id you hold no claim for answers not_found. To start a re-attestation, send its id as claim_id at POST /v1/credentials/reattestations; to end it, POST /v1/credentials/claims/{claim_id}/lapse.
Path parameters
claim_id
string
requiredThe claim's identifier: the id of a claim from POST /v1/credentials/claims, GET /v1/credentials/claims, or a Wallet's claims at GET /v1/credentials/wallets/{wallet_id}. It starts with proof_academic_claim_.
Returns
One claim in full, naming any newer claim that replaced it, so you act on the right one.
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsClaimsRetrieve({ claimId: 'proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_claims_retrieve(claim_id='proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsClaimsRetrieve(claim_id: 'proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsClaimsRetrieve("proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsClaimsRetrieve(claimId: "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsClaimsRetrieve(ctx, "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/credentials/disclosures#Which Wallets you hold a disclosure for
credentials.disclosures.list
This answers which Skill Wallets your organisation holds a disclosure on, and what standing each one is in. No call reads one disclosure on its own, so this page is where your product checks what it asked for against what it holds.
A disclosure is one person's consent for you to read claims already established and held in their Skill Wallet. You get a page of them, each carrying the Wallet it opens, the claims it covers, the end date it was given and status.
Revoked disclosures stay on this page beside live ones and nothing narrows it, so read status on a record before you act on it.
Carry a record's wallet_id to GET /v1/credentials/wallets/{wallet_id}, or its id as disclosure_id to POST /v1/credentials/disclosures/{disclosure_id}/revoke to end one.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
A page of disclosures.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of CredentialDisclosure
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
11 fields of CredentialDisclosure
id
string
requiredThis disclosure's identifier. It starts with proof_academic_wallet_, never changes, and is what you pass as disclosure_id to POST /v1/credentials/disclosures/{disclosure_id}/revoke.
object
always "credential_disclosure"
requiredAlways credential_disclosure. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
wallet_id
string
requiredThe Skill Wallet this disclosure opens to you: the Wallet id you sent at POST /v1/credentials/disclosures, starting with proof_academic_wallet_. Read it at GET /v1/credentials/wallets/{wallet_id} while status is active.
subject_ref
string
optionalThe person the Wallet belongs to: the opaque subject_ref the Wallet carries at GET /v1/credentials/wallets/{wallet_id}, as sent at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_ids
array of string
optionalThe claims the disclosure covers, as claim ids, when you narrowed it to some of the Wallet rather than all of it.
consent_token_id
string
optionalThe subject's consent to you reading what another organisation verified, as you sent it at POST /v1/credentials/disclosures: the id of a consent token from POST /v1/identity/consent_tokens, starting with anchor_pro_consent_.
status
string
requiredactive while you may read the Wallet; revoked once you ended it at POST /v1/credentials/disclosures/{disclosure_id}/revoke or the subject ended it themselves; expired after expires_at.
activerevokedexpiredreused_existing_evidence
boolean
optionalTrue when nothing was re-verified and nothing was re-charged.
expires_at
string · date-time
requiredWhen your access ends, as an RFC 3339 timestamp in UTC; after it, status is expired. Every disclosure has one.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional)
const result = await api.credentialsDisclosuresList({ limit: 25 });// query parameters: limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), starting_after (optional)
result = api.credentials_disclosures_list(limit=25)import os
import requests
# query parameters: limit (optional), starting_after (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional)
$result = $api->credentialsDisclosuresList(limit: 25);<?php
// query parameters: limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), starting_after (optional)
var result = api.credentialsDisclosuresList(25, null);// query parameters: limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), starting_after (optional)
var result = api.CredentialsDisclosuresList(limit: 25);// query parameters: limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional)
result, _, err := client.PROOFAPI.CredentialsDisclosuresList(ctx).Limit(25).Execute()// query parameters: limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/disclosures?limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_disclosure",
"livemode": false,
"mocked": true,
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"status": "active",
"expires_at": "2026-09-01T09:00:00Z",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reused_existing_evidence": true
}
],
"has_more": true
}
/v1/credentials/disclosures#Reuse an existing Wallet entry with consent
credentials.disclosures.create
This records one person's consent for you to read claims their Skill Wallet already holds. So the consent your product is working from is a platform record of its own, named, dated and tied to the one Wallet it covers, and not a line in your own notes.
Send the Wallet as wallet_id, the consent token it rests on, and the date your access should end. You get the disclosure back, with an id starting proof_academic_wallet_.
The Wallet has to exist first, from POST /v1/credentials/wallets. The consent token comes from POST /v1/identity/consent_tokens in ANCHOR.
That id carries the same prefix a Skill Wallet's own id does, so keep it as your disclosure_id and do not send it back as a wallet_id.
Carry the id as disclosure_id to POST /v1/credentials/disclosures/{disclosure_id}/revoke. No call reads one disclosure, so GET /v1/credentials/disclosures is where you see it again.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
wallet_id
string
requiredThe Skill Wallet you want to read: the id of a Wallet from GET /v1/credentials/wallets, starting with proof_academic_wallet_, holding claims another organisation already verified.
claim_ids
array of string
optionalThe claims you want disclosed, by id, when you need only some of the Wallet. Leave it out to ask for the Wallet as a whole.
consent_token_id
string
requiredThe subject's consent to this disclosure: the id of a consent token from POST /v1/identity/consent_tokens, starting with anchor_pro_consent_. One not bound to this subject and purpose is refused with consent_token_missing.
expires_at
string · date-time
requiredWhen your access should end, as an RFC 3339 timestamp in UTC. Required: there is no open ended disclosure.
Returns
The disclosure, with claim_ids for the part of the Wallet this consent reaches.
id
string
requiredThis disclosure's identifier. It starts with proof_academic_wallet_, never changes, and is what you pass as disclosure_id to POST /v1/credentials/disclosures/{disclosure_id}/revoke.
object
always "credential_disclosure"
requiredAlways credential_disclosure. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
wallet_id
string
requiredThe Skill Wallet this disclosure opens to you: the Wallet id you sent at POST /v1/credentials/disclosures, starting with proof_academic_wallet_. Read it at GET /v1/credentials/wallets/{wallet_id} while status is active.
subject_ref
string
optionalThe person the Wallet belongs to: the opaque subject_ref the Wallet carries at GET /v1/credentials/wallets/{wallet_id}, as sent at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_ids
array of string
optionalThe claims the disclosure covers, as claim ids, when you narrowed it to some of the Wallet rather than all of it.
consent_token_id
string
optionalThe subject's consent to you reading what another organisation verified, as you sent it at POST /v1/credentials/disclosures: the id of a consent token from POST /v1/identity/consent_tokens, starting with anchor_pro_consent_.
status
string
requiredactive while you may read the Wallet; revoked once you ended it at POST /v1/credentials/disclosures/{disclosure_id}/revoke or the subject ended it themselves; expired after expires_at.
activerevokedexpiredreused_existing_evidence
boolean
optionalTrue when nothing was re-verified and nothing was re-charged.
expires_at
string · date-time
requiredWhen your access ends, as an RFC 3339 timestamp in UTC; after it, status is expired. Every disclosure has one.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/disclosures" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"wallet_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","consent_token_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expires_at":"2026-09-01T09:00:00Z","claim_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsDisclosuresCreate({
idempotencyKey: crypto.randomUUID(),
proofCredentialDisclosureCreateRequest: {"walletId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","consentTokenId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expiresAt":"2026-09-01T09:00:00Z","claimIds":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/disclosures', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"expires_at": "2026-09-01T09:00:00Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_disclosures_create(body={"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "expires_at": "2026-09-01T09:00:00Z", "claim_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/disclosures',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "expires_at": "2026-09-01T09:00:00Z", "claim_ids": ["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsDisclosuresCreate($idempotencyKey, json_decode('{"wallet_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","consent_token_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expires_at":"2026-09-01T09:00:00Z","claim_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/disclosures');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"wallet_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","consent_token_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","expires_at":"2026-09-01T09:00:00Z","claim_ids":["sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"]}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsDisclosuresCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/disclosures"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"expires_at": "2026-09-01T09:00:00Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsDisclosuresCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/disclosures");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"expires_at": "2026-09-01T09:00:00Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsDisclosuresCreate(ctx).IdempotencyKey(key).ProofCredentialDisclosureCreateRequest(body).Execute()body := strings.NewReader(`{
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"expires_at": "2026-09-01T09:00:00Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/disclosures", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_disclosure",
"livemode": false,
"mocked": true,
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"status": "active",
"expires_at": "2026-09-01T09:00:00Z",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reused_existing_evidence": true
}
/v1/credentials/disclosures/{disclosure_id}/revoke#End a disclosure on a Skill Wallet
credentials.disclosures.revoke
This ends one disclosure, whatever date you gave it when you asked. So a consent your product took for one piece of work can be given back the day that work is done, and not held on past it.
Send the disclosure's id in the path. Send no request body; you get the disclosure back, the same record GET /v1/credentials/disclosures lists.
The disclosure has to exist first, from POST /v1/credentials/disclosures.
The subject can end a disclosure themselves, so one you hold may be ended without you calling anything.
A disclosure that is no longer live is refused with conflict. Nothing here puts one back: ask again at POST /v1/credentials/disclosures.
Path parameters
disclosure_id
string
requiredThe disclosure's identifier: the id of a disclosure from POST /v1/credentials/disclosures or GET /v1/credentials/disclosures. It starts with proof_academic_wallet_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The revoked disclosure.
id
string
requiredThis disclosure's identifier. It starts with proof_academic_wallet_, never changes, and is what you pass as disclosure_id to POST /v1/credentials/disclosures/{disclosure_id}/revoke.
object
always "credential_disclosure"
requiredAlways credential_disclosure. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
wallet_id
string
requiredThe Skill Wallet this disclosure opens to you: the Wallet id you sent at POST /v1/credentials/disclosures, starting with proof_academic_wallet_. Read it at GET /v1/credentials/wallets/{wallet_id} while status is active.
subject_ref
string
optionalThe person the Wallet belongs to: the opaque subject_ref the Wallet carries at GET /v1/credentials/wallets/{wallet_id}, as sent at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_ids
array of string
optionalThe claims the disclosure covers, as claim ids, when you narrowed it to some of the Wallet rather than all of it.
consent_token_id
string
optionalThe subject's consent to you reading what another organisation verified, as you sent it at POST /v1/credentials/disclosures: the id of a consent token from POST /v1/identity/consent_tokens, starting with anchor_pro_consent_.
status
string
requiredactive while you may read the Wallet; revoked once you ended it at POST /v1/credentials/disclosures/{disclosure_id}/revoke or the subject ended it themselves; expired after expires_at.
activerevokedexpiredreused_existing_evidence
boolean
optionalTrue when nothing was re-verified and nothing was re-charged.
expires_at
string · date-time
requiredWhen your access ends, as an RFC 3339 timestamp in UTC; after it, status is expired. Every disclosure has one.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsDisclosuresRevoke({ disclosureId: '{disclosure_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_disclosures_revoke(disclosure_id='{disclosure_id}')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsDisclosuresRevoke(disclosure_id: '{disclosure_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsDisclosuresRevoke("{disclosure_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsDisclosuresRevoke(disclosureId: "{disclosure_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsDisclosuresRevoke(ctx, "{disclosure_id}").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/disclosures/%7Bdisclosure_id%7D/revoke", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_wallet_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_disclosure",
"livemode": false,
"mocked": true,
"wallet_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"status": "revoked",
"expires_at": "2026-09-01T09:00:00Z",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
],
"consent_token_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"reused_existing_evidence": true
}
/v1/credentials/licences#See who is licensed to practise
credentials.licences.list
A licence here is one person's permission from a regulator or a professional body to work in an occupation, and this reads back the ones on record for your subjects. Each row is the whole licence, so your product can show what people hold without a second call for each one.
You get one record for each licence: what it is for, who holds it, its issue and expiry dates, and whether the law requires it. Nothing here creates a licence; you read what a person already holds.
Where you already hold a licence id, from a row here or from a Wallet's licences, GET /v1/credentials/licences/{licence_id} answers with that one record.
Query parameters
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
subject_ref
string
optionalRestricts the page to licences held by one person: the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Leave it out to get every subject you may see.
status
string
optionalRestricts the page to licences in one status: valid while the licence stands, expiring as expires_on draws near, expired once that day has passed, revoked when the issuer withdrew it. Leave it out to get every status.
validexpiringexpiredrevokedReturns
The issuer's name never comes back on a row here.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of Licence
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
11 fields of Licence
id
string
requiredThis licence's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as licence_id to GET /v1/credentials/licences/{licence_id}.
object
always "licence"
requiredAlways licence. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe person who holds the licence, as the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
licence_kind
string
requiredWhat the licence is for, as a code such as electrical_installation_practitioner. Read statutorily_required_for for the occupations the law ties it to.
issuer_id
string
optionalThe body that granted the licence, as the id of an issuer from GET /v1/credentials/issuers, starting with proof_academic_verification_. Read it at GET /v1/credentials/issuers/{issuer_id} for its kind and how it's reached.
licence_number_masked
string
optionalMasked. The number itself belongs to the holder.
status
string
requiredvalidexpiringexpiredrevokedissued_on
string · date
optionalThe day the issuer granted the licence, as a YYYY-MM-DD date.
expires_on
string · date
optionalThe day the licence runs out, as a YYYY-MM-DD date. Once it passes, status is expired, the Wallet entry expires on its own, and you're told if you hold a live disclosure.
statutorily_required_for
array of string
optionalOccupations where the law requires this licence.
statutory_block
boolean
requiredTrue when this licence is legally required and is not currently valid. Read it as a gate on its own. It is not a factor to weigh against other evidence, and nothing else in the Wallet substitutes for it.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
const result = await api.credentialsLicencesList({ limit: 25, subjectRef: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', status: 'valid' });// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
result = api.credentials_licences_list(limit=25, subject_ref='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', status='valid')import os
import requests
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
$result = $api->credentialsLicencesList(limit: 25, subject_ref: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', status: 'valid');<?php
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
import com.droomwork.sdk.model.*;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
var result = api.credentialsLicencesList(25, null, "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", ProofLicenceStatus.fromValue("valid"));// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
var result = api.CredentialsLicencesList(limit: 25, subjectRef: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", status: ProofLicenceStatus.Valid);// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
result, _, err := client.PROOFAPI.CredentialsLicencesList(ctx).Limit(25).SubjectRef("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Status(droomwork.ProofLicenceStatus("valid")).Execute()// query parameters: limit (optional), starting_after (optional), subject_ref (optional), status (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/licences?limit=25&subject_ref=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&status=valid", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "licence",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_kind": "electrical_installation_practitioner",
"status": "valid",
"statutory_block": true,
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_number_masked": "example",
"issued_on": "2026-09-01",
"expires_on": "2026-09-01",
"statutorily_required_for": [
"example"
]
}
],
"has_more": true
}
/v1/credentials/licences/{licence_id}#Read one licence, and whether it blocks
credentials.licences.retrieve
This opens one licence: a person's permission from a regulator or a professional body to work in an occupation. The record names the occupations the law ties it to, so you can tell which work this licence is a condition of, and not guess from its licence_kind code.
Send the licence's id in the path. You get that one licence back, with status for where it stands, issued_on and expires_on for its dates, and statutory_block for whether it blocks.
Where the law requires a licence, statutory_block is the field to read and status is not. Read it as a gate on its own, not as one input you weigh against the rest of the record.
A licence_id also comes from a Wallet's licences at GET /v1/credentials/wallets/{wallet_id}. An id you hold no record for is refused.
Path parameters
licence_id
string
requiredThe licence's identifier: the id of a licence from GET /v1/credentials/licences or a Wallet's licences at GET /v1/credentials/wallets/{wallet_id}. It starts with proof_academic_claim_.
Returns
The licence, with its number masked, so a reviewer reads the record and never the number.
id
string
requiredThis licence's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as licence_id to GET /v1/credentials/licences/{licence_id}.
object
always "licence"
requiredAlways licence. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe person who holds the licence, as the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
licence_kind
string
requiredWhat the licence is for, as a code such as electrical_installation_practitioner. Read statutorily_required_for for the occupations the law ties it to.
issuer_id
string
optionalThe body that granted the licence, as the id of an issuer from GET /v1/credentials/issuers, starting with proof_academic_verification_. Read it at GET /v1/credentials/issuers/{issuer_id} for its kind and how it's reached.
licence_number_masked
string
optionalMasked. The number itself belongs to the holder.
status
string
requiredvalidexpiringexpiredrevokedissued_on
string · date
optionalThe day the issuer granted the licence, as a YYYY-MM-DD date.
expires_on
string · date
optionalThe day the licence runs out, as a YYYY-MM-DD date. Once it passes, status is expired, the Wallet entry expires on its own, and you're told if you hold a live disclosure.
statutorily_required_for
array of string
optionalOccupations where the law requires this licence.
statutory_block
boolean
requiredTrue when this licence is legally required and is not currently valid. Read it as a gate on its own. It is not a factor to weigh against other evidence, and nothing else in the Wallet substitutes for it.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsLicencesRetrieve({ licenceId: '{licence_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_licences_retrieve(licence_id='{licence_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsLicencesRetrieve(licence_id: '{licence_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsLicencesRetrieve("{licence_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsLicencesRetrieve(licenceId: "{licence_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsLicencesRetrieve(ctx, "{licence_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/licences/%7Blicence_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "licence",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_kind": "electrical_installation_practitioner",
"status": "valid",
"statutory_block": true,
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"licence_number_masked": "example",
"issued_on": "2026-09-01",
"expires_on": "2026-09-01",
"statutorily_required_for": [
"example"
]
}
/v1/credentials/reattestations#Start a re-attestation before a claim expires
credentials.reattestations.create
This starts the refresh of a competence claim, and hands you the step the worker has to take and the link they take it at. So your product can show that worker what to do and where, from the same call that starts the refresh.
Send the claim_id of the claim you are refreshing, one ageing toward its expiry or already lapsed. You get the re-attestation back, carrying path: what the worker has to do, where they do it, and how long it should take.
A claim has to exist first, from POST /v1/credentials/claims.
The id you get back starts proof_academic_claim_, the same prefix the claim carries, so keep the two apart in your store.
Carry the id forward as reattestation_id to GET /v1/credentials/reattestations/{reattestation_id}, which is the only call that reads one back.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
claim_id
string
requiredThe claim to refresh: the id of a competence claim from POST /v1/credentials/claims or GET /v1/credentials/claims, starting with proof_academic_claim_, that's ageing toward expires_at or has already lapsed.
Returns
The re-attestation, with the path the worker follows.
id
string
requiredThis re-attestation's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as reattestation_id to GET /v1/credentials/reattestations/{reattestation_id}.
object
always "reattestation"
requiredAlways reattestation. Tells you which kind of record you are looking at, so one handler can read any response.
claim_id
string
requiredThe claim being refreshed: the id you sent at POST /v1/credentials/reattestations, starting with proof_academic_claim_. Once the worker finishes, new_claim_id carries the replacement and this claim's superseded_by points to it.
subject_ref
string
optionalThe worker whose claim is being refreshed, as the opaque subject_ref the claim carries: the one you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
status
string
requiredWhere the refresh stands: notified once the notice is sent, in_progress while the worker follows the path, completed once a new claim replaces the old one, expired if the claim lapsed first.
notifiedin_progresscompletedexpiredpath
object
requiredWhat the worker actually has to do, rather than a warning that something is about to lapse.
3 fields
action
string
requiredWhat the worker has to do, in words you can show them, such as upload a current licence certificate.
url
string · uri
requiredThe link the worker follows to do it. The notice carries it, and you can show it to them yourself as well.
estimated_minutes
integer
optionalHow long the step should take the worker, in minutes. Absent when there is no estimate.
notice_sent_at
string · date-time
optionalWhen the worker was sent the notice carrying this path, as an RFC 3339 timestamp in UTC.
claim_expires_at
string · date
optionalThe day the claim being refreshed expires, as a calendar date, YYYY-MM-DD. The refresh has to finish before it, or the claim lapses and the re-attestation is expired.
new_claim_id
string · nullable
optionalThe id of the claim that replaced the old one once status is completed; null until then. It starts with proof_academic_claim_ and is read at GET /v1/credentials/claims/{claim_id}; the old claim stays, with this in its superseded_by.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/reattestations" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"claim_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsReattestationsCreate({
idempotencyKey: crypto.randomUUID(),
proofReattestationCreateRequest: {"claimId":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/reattestations', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_reattestations_create(body={"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/reattestations',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsReattestationsCreate($idempotencyKey, json_decode('{"claim_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/reattestations');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"claim_id":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsReattestationsCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/reattestations"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsReattestationsCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/reattestations");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsReattestationsCreate(ctx).IdempotencyKey(key).ProofReattestationCreateRequest(body).Execute()body := strings.NewReader(`{
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/reattestations", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "reattestation",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"status": "notified",
"path": {
"action": "upload a current licence certificate",
"url": "https://files.sandbox.droomwork.com/example",
"estimated_minutes": 1
},
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"notice_sent_at": "2026-09-01T09:00:00Z",
"claim_expires_at": "2026-09-01",
"new_claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/credentials/reattestations/{reattestation_id}#The step a worker has to take
credentials.reattestations.retrieve
A re-attestation is the refresh of one competence claim: what the worker has to do about it, and where. One stored id is all your product needs to come back to it, because the record names the worker and the claim as well as the step.
Send the re-attestation's id in the path as reattestation_id. You get that record back: claim_id and subject_ref for what and who it is about, and path for the action, the link and the estimate in minutes.
The claim itself is at GET /v1/credentials/claims/{claim_id}, using the claim_id this record carries. An id you hold no record for is refused.
Path parameters
reattestation_id
string
requiredThe re-attestation's identifier: the id of a re-attestation from POST /v1/credentials/reattestations. It starts with proof_academic_claim_.
Returns
The re-attestation, with notice_sent_at once the worker's notice has gone out, so your own reminder can follow it.
id
string
requiredThis re-attestation's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as reattestation_id to GET /v1/credentials/reattestations/{reattestation_id}.
object
always "reattestation"
requiredAlways reattestation. Tells you which kind of record you are looking at, so one handler can read any response.
claim_id
string
requiredThe claim being refreshed: the id you sent at POST /v1/credentials/reattestations, starting with proof_academic_claim_. Once the worker finishes, new_claim_id carries the replacement and this claim's superseded_by points to it.
subject_ref
string
optionalThe worker whose claim is being refreshed, as the opaque subject_ref the claim carries: the one you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
status
string
requiredWhere the refresh stands: notified once the notice is sent, in_progress while the worker follows the path, completed once a new claim replaces the old one, expired if the claim lapsed first.
notifiedin_progresscompletedexpiredpath
object
requiredWhat the worker actually has to do, rather than a warning that something is about to lapse.
3 fields
action
string
requiredWhat the worker has to do, in words you can show them, such as upload a current licence certificate.
url
string · uri
requiredThe link the worker follows to do it. The notice carries it, and you can show it to them yourself as well.
estimated_minutes
integer
optionalHow long the step should take the worker, in minutes. Absent when there is no estimate.
notice_sent_at
string · date-time
optionalWhen the worker was sent the notice carrying this path, as an RFC 3339 timestamp in UTC.
claim_expires_at
string · date
optionalThe day the claim being refreshed expires, as a calendar date, YYYY-MM-DD. The refresh has to finish before it, or the claim lapses and the re-attestation is expired.
new_claim_id
string · nullable
optionalThe id of the claim that replaced the old one once status is completed; null until then. It starts with proof_academic_claim_ and is read at GET /v1/credentials/claims/{claim_id}; the old claim stays, with this in its superseded_by.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsReattestationsRetrieve({ reattestationId: '{reattestation_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_reattestations_retrieve(reattestation_id='{reattestation_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsReattestationsRetrieve(reattestation_id: '{reattestation_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsReattestationsRetrieve("{reattestation_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsReattestationsRetrieve(reattestationId: "{reattestation_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsReattestationsRetrieve(ctx, "{reattestation_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/reattestations/%7Breattestation_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "reattestation",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"status": "notified",
"path": {
"action": "upload a current licence certificate",
"url": "https://files.sandbox.droomwork.com/example",
"estimated_minutes": 1
},
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"notice_sent_at": "2026-09-01T09:00:00Z",
"claim_expires_at": "2026-09-01",
"new_claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/credentials/appeals#Challenge a result on a subject's behalf
credentials.appeals.create
A subject can dispute a result PROOF returned about them, and you file it here. So your product can give somebody a way to push back on what was said about them, with their reason bound to the one result it disputes.
Send the subject_ref the result under appeal carries, that result in against, and the subject's reason in grounds. You get the appeal back with an id starting proof_academic_claim_ and status of received.
The result under appeal has to exist first, from POST /v1/credentials/verifications, POST /v1/credentials/document_analyses or POST /v1/credentials/claims; a licence has no create to call.
A subject_ref you have not used before files an appeal about somebody your other records do not name. Keep a copy of anything you link in supporting_document_url; it is not returned.
Carry the id forward as appeal_id to GET /v1/credentials/appeals/{appeal_id}, the only call that reads one back.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Body
subject_ref
string
requiredThe person whose result is being challenged: the same opaque subject_ref the result carries, the one you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.
against
object
requiredThe result being challenged, named by its kind and id. One appeal challenges one result.
2 fields
kind
string
requiredWhich kind of result you are challenging: verification (an issuer's answer), document_analysis (a document finding), claim (a competence claim) or licence (a statutory licence). It says which record id names.
verificationdocument_analysisclaimlicenceid
string
requiredThe id of the record kind names, from POST /v1/credentials/verifications, POST /v1/credentials/document_analyses, POST /v1/credentials/claims or GET /v1/credentials/licences. It starts with proof_academic_verification_ or proof_academic_claim_.
grounds
string
requiredWhy the subject says the result is wrong, in plain words. It stays on the appeal and is read at review, so give the facts a decision can rest on.
supporting_document_url
string · uri · write only
optionalA link to a document that supports the appeal, if the subject has one. It is taken at filing and never returned on the appeal.
Returns
The appeal, with the date it is due.
id
string
requiredThe appeal's identifier, first returned by POST /v1/credentials/appeals and read back at GET /v1/credentials/appeals/{appeal_id}. It starts with proof_academic_claim_ and never changes; pass it as appeal_id.
object
always "appeal"
requiredAlways appeal. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe opaque reference you chose for the person and sent as subject_ref at POST /v1/credentials/appeals, the same one the result under appeal carries, echoed as you sent it. Not a name and not an identifier.
against
object
requiredThe result under appeal, by its kind and id, as you named it when you filed.
2 fields
kind
string
requiredWhich kind of result is under appeal, as you named it at POST /v1/credentials/appeals: verification, document_analysis, claim or licence. It says which record id names.
verificationdocument_analysisclaimlicenceid
string
requiredThe id of the record under appeal, as you sent it at POST /v1/credentials/appeals, whichever kind names. It starts with proof_academic_verification_ (verification, document analysis) or proof_academic_claim_ (claim, licence).
grounds
string
optionalWhy the subject says the result is wrong, as you stated it when you filed.
status
string
requiredWhere the appeal stands: received once filed, in_review while it is being decided, upheld if the result was overturned and standing restored, rejected if the result stands. Only upheld fills restored_claim_ids.
receivedin_reviewupheldrejecteddue_by
string · date
requiredThe published service level. An appeal has a date, not an intention.
outcome_detail
string · nullable
optionalThe reasons given with the decision, in plain words. null while there is no decision to explain.
restored_claim_ids
array of string
optionalSet when the appeal was upheld. These propagate to every live disclosure.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/appeals" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","against":{"kind":"verification","id":"evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},"grounds":"The classification does not reflect how the work is actually controlled.","supporting_document_url":"https://files.sandbox.droomwork.com/example"}'import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsAppealsCreate({
idempotencyKey: crypto.randomUUID(),
proofAppealCreateRequest: {"subjectRef":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","against":{"kind":"verification","id":"evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},"grounds":"The classification does not reflect how the work is actually controlled.","supportingDocumentUrl":"https://files.sandbox.droomwork.com/example"},
});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/appeals', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"against": {
"kind": "verification",
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"grounds": "The classification does not reflect how the work is actually controlled.",
"supporting_document_url": "https://files.sandbox.droomwork.com/example"
}),
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_appeals_create(body={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "against": {"kind": "verification", "id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}, "grounds": "The classification does not reflect how the work is actually controlled.", "supporting_document_url": "https://files.sandbox.droomwork.com/example"})import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/appeals',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
json={"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "against": {"kind": "verification", "id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"}, "grounds": "The classification does not reflect how the work is actually controlled.", "supporting_document_url": "https://files.sandbox.droomwork.com/example"},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsAppealsCreate($idempotencyKey, json_decode('{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","against":{"kind":"verification","id":"evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},"grounds":"The classification does not reflect how the work is actually controlled.","supporting_document_url":"https://files.sandbox.droomwork.com/example"}', true));<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/appeals');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => '{"subject_ref":"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z","against":{"kind":"verification","id":"evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"},"grounds":"The classification does not reflect how the work is actually controlled.","supporting_document_url":"https://files.sandbox.droomwork.com/example"}',
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsAppealsCreate(idempotencyKey, body);var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/appeals"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"against": {
"kind": "verification",
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"grounds": "The classification does not reflect how the work is actually controlled.",
"supporting_document_url": "https://files.sandbox.droomwork.com/example"
}
"""))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsAppealsCreate(idempotencyKey, body);using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/appeals");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
request.Content = new StringContent("""
{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"against": {
"kind": "verification",
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"grounds": "The classification does not reflect how the work is actually controlled.",
"supporting_document_url": "https://files.sandbox.droomwork.com/example"
}
""", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsAppealsCreate(ctx).IdempotencyKey(key).ProofAppealCreateRequest(body).Execute()body := strings.NewReader(`{
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"against": {
"kind": "verification",
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"grounds": "The classification does not reflect how the work is actually controlled.",
"supporting_document_url": "https://files.sandbox.droomwork.com/example"
}`)
req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/appeals", body)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "appeal",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"against": {
"kind": "verification",
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"status": "received",
"due_by": "2026-09-01",
"grounds": "The classification does not reflect how the work is actually controlled.",
"outcome_detail": "example",
"restored_claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}
/v1/credentials/appeals/{appeal_id}#Check an appeal you filed
credentials.appeals.retrieve
An appeal is one subject's dispute of one result PROOF returned about them, and this reads one back by its identifier. So the words the subject gave stay on the appeal, and your product can show a dispute it filed without keeping a copy of them.
Send the appeal's id in the path as appeal_id. You get that appeal back with the result it is against and the grounds it was filed on.
An appeal_id nothing was filed under answers not_found. Nothing else waits on this call.
Path parameters
appeal_id
string
requiredThe appeal's identifier, from the id of an appeal you filed at POST /v1/credentials/appeals. It starts with proof_academic_claim_.
Returns
The appeal, with the due_by date you never sent, so your product can show when an answer is due.
id
string
requiredThe appeal's identifier, first returned by POST /v1/credentials/appeals and read back at GET /v1/credentials/appeals/{appeal_id}. It starts with proof_academic_claim_ and never changes; pass it as appeal_id.
object
always "appeal"
requiredAlways appeal. Tells you which kind of record you are looking at, so one handler can read any response.
subject_ref
string
requiredThe opaque reference you chose for the person and sent as subject_ref at POST /v1/credentials/appeals, the same one the result under appeal carries, echoed as you sent it. Not a name and not an identifier.
against
object
requiredThe result under appeal, by its kind and id, as you named it when you filed.
2 fields
kind
string
requiredWhich kind of result is under appeal, as you named it at POST /v1/credentials/appeals: verification, document_analysis, claim or licence. It says which record id names.
verificationdocument_analysisclaimlicenceid
string
requiredThe id of the record under appeal, as you sent it at POST /v1/credentials/appeals, whichever kind names. It starts with proof_academic_verification_ (verification, document analysis) or proof_academic_claim_ (claim, licence).
grounds
string
optionalWhy the subject says the result is wrong, as you stated it when you filed.
status
string
requiredWhere the appeal stands: received once filed, in_review while it is being decided, upheld if the result was overturned and standing restored, rejected if the result stands. Only upheld fills restored_claim_ids.
receivedin_reviewupheldrejecteddue_by
string · date
requiredThe published service level. An appeal has a date, not an intention.
outcome_detail
string · nullable
optionalThe reasons given with the decision, in plain words. null while there is no decision to explain.
restored_claim_ids
array of string
optionalSet when the appeal was upheld. These propagate to every live disclosure.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsAppealsRetrieve({ appealId: '{appeal_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_appeals_retrieve(appeal_id='{appeal_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsAppealsRetrieve(appeal_id: '{appeal_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsAppealsRetrieve("{appeal_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsAppealsRetrieve(appealId: "{appeal_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsAppealsRetrieve(ctx, "{appeal_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/appeals/%7Bappeal_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "appeal",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"against": {
"kind": "verification",
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
},
"status": "received",
"due_by": "2026-09-01",
"grounds": "The classification does not reflect how the work is actually controlled.",
"outcome_detail": "example",
"restored_claim_ids": [
"sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
]
}
/v1/credentials/readiness#What PROOF needs before any verification runs
credentials.readiness.retrieve
PROOF depends on facts it does not own, and this is where they are written down. So a dependency on another module is something you can plan for before you build against credentials.
Send no parameters. You get always_enforced, the gates that hold in every mode whatever you attest to, and one rows entry per fact, naming the fact, its owner and the assurance it has to reach.
Nothing else waits on this call.
Returns
The report, with issuers_unreachable so silence from a register is never read as a pass.
object
always "readiness_report"
requiredAlways readiness_report. Tells you which kind of record you are looking at, so one handler can read any response.
module
string
requiredThe module whose readiness endpoint you called, such as GET /v1/payroll/readiness: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance) or route (payouts).
anchorproofrailflowmatchrunremitroutemode
string
requiredintegrated means a required fact comes from the Droomwork module that owns it; standalone means you supply it yourself under an attestation. The checks are the same in both, and what each fact is worth is recorded on its row as held.
integratedstandaloneready
boolean
requiredtrue when every row is satisfied and the module has what it needs from you. false when a required fact is missing or held at too low an assurance; rows says which.
rows
array of ReadinessRow
requiredOne row per fact the module requires: who owns it, the assurance it needs, what you hold and whether that satisfies it. The rows that are not satisfied are what to bring.
8 fields of ReadinessRow
fact
string
requiredNamed for what it is, not for who supplies it.
owner
string
requiredThe sibling module that owns this fact when running integrated.
contract_row
string
optionalrequired
string
requiredHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedheld
one of
requiredAssuranceorsource
string · nullable
optionalintegratedstandalonenullsatisfied
boolean
requiredmissing_because
string · nullable
optionalnot_suppliedassurance_too_lownullalways_enforced
array of string
optionalGates that hold in every mode and cannot be attested away. Consent before any source is queried, bank account validation, the tax identifiers returns are filed under, and duplicate detection within the organisation's own population.
issuers_unreachable
array of string
optionalThe issuers you can't reach right now. A verification sent to one of them comes back unverified with the source named, never a pass.
subjects_without_passport
integer
optionalHow many of your subjects have no live Passport. Nothing verifies for them until they are anchored, whether by ANCHOR or by an identity you supply.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/readiness" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsReadinessRetrieve({});const response = await fetch('https://sandbox.droomwork.io/v1/credentials/readiness', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_readiness_retrieve()import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/readiness',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsReadinessRetrieve();<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/readiness');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsReadinessRetrieve();var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/readiness"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsReadinessRetrieve();using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/readiness");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsReadinessRetrieve(ctx).Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/readiness", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "readiness_report",
"module": "proof",
"mode": "integrated",
"ready": true,
"rows": [
{
"fact": "anchored_subject",
"owner": "anchor",
"contract_row": "6.5.1",
"required": "attested",
"held": "attested",
"source": "integrated",
"satisfied": true,
"missing_because": null
}
],
"always_enforced": [
"consent_token",
"bank_account_validation",
"tax_identifier_validation",
"duplicate_detection"
],
"issuers_unreachable": [],
"subjects_without_passport": 1
}
/v1/credentials/claims/{claim_id}/lapse#Record that a competence no longer counts
credentials.claims.lapse
This records that the evidence behind a claim has stopped being current. That is not the same as the claim having been wrong, which is why the record stays: you can still read that the person held it.
Send the claim's id in the path and send no request body. You get the claim back, and decay_state is the field this call moves.
The claim has to exist first, from POST /v1/credentials/claims.
If the finding itself is what you are disputing, this is not the call: an appeal at POST /v1/credentials/appeals names a claim by its id.
A claim that has already lapsed answers conflict, and an id you hold no claim for answers not_found. Afterwards you can send the same id as claim_id at POST /v1/credentials/reattestations.
Path parameters
claim_id
string
requiredThe claim's identifier: the id of a claim from POST /v1/credentials/claims, GET /v1/credentials/claims, or a Wallet's claims at GET /v1/credentials/wallets/{wallet_id}. It starts with proof_academic_claim_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The claim in full, with verification_id where it had one, so the evidence stays traceable.
id
string
requiredThe claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.
object
always "competence_claim"
requiredAlways competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
subject_ref
string
requiredThe opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.
claim_code
string
requiredWhich competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.
framework_version
string
optionalThe framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.
level
integer · minimum 1
requiredHow high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.
evidence_class
string
requiredOrdered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.
e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstratedevidence_date
string · date
requiredThe day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.
issuing_method
string
optionalHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualdecay_state
string
requiredWhere a claim sits on its published decay function. It moves on its own and cannot be quietly extended.
currentageingstalelapsedexpires_at
string · date
requiredSet when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.
decay_function
string
optionalThe published function this claim ages on. Readable so the expiry can be predicted rather than discovered.
verification_id
string · nullable
optionalThe verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.
superseded_by
string · nullable
optionalSet when a re-attestation replaced this claim. The old one stays.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsClaimsLapse({ claimId: 'proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_claims_lapse(claim_id='proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsClaimsLapse(claim_id: 'proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsClaimsLapse("proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsClaimsLapse(claimId: "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsClaimsLapse(ctx, "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/claims/proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/lapse", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": false,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "example",
"level": 1,
"evidence_class": "e1_claimed",
"evidence_date": "2026-09-01",
"decay_state": "current",
"expires_at": "2026-09-01",
"framework_version": "2026.08.1",
"issuing_method": "register_api",
"decay_function": "linear over 24 months from evidence_date",
"verification_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"superseded_by": "usr_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
/v1/credentials/verifications/{verification_id}/verify#Record that the issuer confirmed the credential
credentials.verifications.verify
The issuer confirmed the credential, and this records it. Of the three outcomes it is the only one that produces a competence claim, so what your product reads later is a record in its own right and not a closed check.
Name the verification in the path and send no request body. You get the verification back with outcome now verified.
The verification must exist, from POST /v1/credentials/verifications, and its outcome must still be pending.
Verifying publishes verification.completed. The claim it produces names this verification in verification_id and is listed at GET /v1/credentials/claims. A verification somebody has already decided is refused with conflict.
Path parameters
verification_id
string
requiredThe verification's identifier: the id returned by POST /v1/credentials/verifications or listed by GET /v1/credentials/verifications. It starts with proof_academic_verification_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
created_at, where there is one, is not when the issuer answered.
id
string
requiredThe verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.
object
always "credential_verification"
requiredAlways credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
outcome
string
requiredFour values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.
pendingverifiedunverifiedcontradictedsubject_ref
string
requiredThe opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.
passport_id
string
optionalThe live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.
identity_assurance
string
optionalHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedissuer_id
string
requiredThe issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.
issuer_name
string
optionalThe issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.
method
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualcredential_kind
string
optionalWhat kind of credential was checked, as you sent it, such as bachelor_degree.
evidence_date
string · date · nullable
optionalWhen the evidence was gathered, not when it was read.
unresolved_source
string · nullable
optionalNamed when the outcome is unverified. Silence always names its source.
contradiction_detail
string · nullable
optionalWhat the issuer actually said, where it disagreed.
service_level_due
string · date · nullable
optionalPresent while pending. When the assisted path is due to answer.
claim_id
string · nullable
optionalThe competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.
cached
boolean
optionalTrue when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsVerificationsVerify({ verificationId: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_verifications_verify(verification_id='proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsVerificationsVerify(verification_id: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsVerificationsVerify("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsVerificationsVerify(verificationId: "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsVerificationsVerify(ctx, "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/verify", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_verification",
"livemode": false,
"mocked": true,
"outcome": "verified",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "example",
"credential_kind": "bachelor_degree",
"evidence_date": "2026-09-01",
"unresolved_source": "example",
"contradiction_detail": "example",
"service_level_due": "2026-09-01",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"cached": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/verifications/{verification_id}/mark_unverified#Close a check the issuer did not answer
credentials.verifications.mark_unverified
The issuer did not answer, and this closes the check. No competence claim is produced, so nothing you read later says the person holds this credential.
Name the verification in the path and send no request body. You get the verification back with outcome now unverified.
The verification must exist, from POST /v1/credentials/verifications, and its outcome must still be pending.
Never read an unverified outcome as the credential being false.
Marking a check unverified publishes verification.completed, the same event a confirmation publishes, so read outcome before you act. A verification somebody has already decided is refused with conflict.
Path parameters
verification_id
string
requiredThe verification's identifier: the id returned by POST /v1/credentials/verifications or listed by GET /v1/credentials/verifications. It starts with proof_academic_verification_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The verification, with the source that never answered named, so the gap can be chased.
id
string
requiredThe verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.
object
always "credential_verification"
requiredAlways credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
outcome
string
requiredFour values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.
pendingverifiedunverifiedcontradictedsubject_ref
string
requiredThe opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.
passport_id
string
optionalThe live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.
identity_assurance
string
optionalHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedissuer_id
string
requiredThe issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.
issuer_name
string
optionalThe issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.
method
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualcredential_kind
string
optionalWhat kind of credential was checked, as you sent it, such as bachelor_degree.
evidence_date
string · date · nullable
optionalWhen the evidence was gathered, not when it was read.
unresolved_source
string · nullable
optionalNamed when the outcome is unverified. Silence always names its source.
contradiction_detail
string · nullable
optionalWhat the issuer actually said, where it disagreed.
service_level_due
string · date · nullable
optionalPresent while pending. When the assisted path is due to answer.
claim_id
string · nullable
optionalThe competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.
cached
boolean
optionalTrue when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsVerificationsMarkUnverified({ verificationId: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_verifications_mark_unverified(verification_id='proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsVerificationsMarkUnverified(verification_id: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsVerificationsMarkUnverified("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsVerificationsMarkUnverified(verificationId: "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsVerificationsMarkUnverified(ctx, "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/mark_unverified", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_verification",
"livemode": false,
"mocked": true,
"outcome": "unverified",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "example",
"credential_kind": "bachelor_degree",
"evidence_date": "2026-09-01",
"unresolved_source": "example",
"contradiction_detail": "example",
"service_level_due": "2026-09-01",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"cached": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/verifications/{verification_id}/contradict#Mark a credential the issuer denies
credentials.verifications.contradict
This records that the issuer denied the credential. It is the only one of the three outcomes with an event of its own, so a review step in your product can listen for contradictions alone and stay quiet for every other check that closes.
Name the verification in the path and send no request body. You get the verification back with outcome now contradicted.
The verification must exist, from POST /v1/credentials/verifications, and its outcome must still be pending.
A contradiction is a statement the issuer made, and no timeout produces one.
Contradicting publishes verification.contradicted, and no competence claim is produced. A verification somebody has already decided is refused with conflict.
Path parameters
verification_id
string
requiredThe verification's identifier: the id returned by POST /v1/credentials/verifications or listed by GET /v1/credentials/verifications. It starts with proof_academic_verification_.
Headers
Idempotency-Key
string
requiredA key you make up for this request, such as a UUID, up to 255 characters. Sending the same key with the same body returns the original response, marked Droomwork-Idempotent-Replay: true; sending it with a different body is refused with idempotency_key_reused.
Returns
The verification, with contradiction_detail for what the issuer actually said, so the subject can answer it.
id
string
requiredThe verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.
object
always "credential_verification"
requiredAlways credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm this record is in: false is the sandbox, true is live. Read it before you act on anything.
mocked
boolean
requiredWhere these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.
outcome
string
requiredFour values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.
pendingverifiedunverifiedcontradictedsubject_ref
string
requiredThe opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.
passport_id
string
optionalThe live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.
identity_assurance
string
optionalHow a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.
attestedverifiedissuer_id
string
requiredThe issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.
issuer_name
string
optionalThe issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.
method
string
requiredHow the issuer is reached. An assisted path publishes a service level.
register_apiissuer_portalassisted_manualcredential_kind
string
optionalWhat kind of credential was checked, as you sent it, such as bachelor_degree.
evidence_date
string · date · nullable
optionalWhen the evidence was gathered, not when it was read.
unresolved_source
string · nullable
optionalNamed when the outcome is unverified. Silence always names its source.
contradiction_detail
string · nullable
optionalWhat the issuer actually said, where it disagreed.
service_level_due
string · date · nullable
optionalPresent while pending. When the assisted path is due to answer.
claim_id
string · nullable
optionalThe competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.
cached
boolean
optionalTrue when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.
created_at
string · date-time
optionalWhen the record was created, as an RFC 3339 timestamp in UTC.
Other responses
Errors it can return
curl -X POST "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsVerificationsContradict({ verificationId: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict', {
method: 'POST',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_verifications_contradict(verification_id='proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z')import os
import uuid
import requests
response = requests.request(
'POST',
'https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
'Idempotency-Key': str(uuid.uuid4()),
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsVerificationsContradict(verification_id: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsVerificationsContradict("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.header("Idempotency-Key", UUID.randomUUID().toString())
.method("POST", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsVerificationsContradict(verificationId: "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsVerificationsContradict(ctx, "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Execute()req, _ := http.NewRequest("POST", "https://sandbox.droomwork.io/v1/credentials/verifications/proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z/contradict", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.NewString())
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "credential_verification",
"livemode": false,
"mocked": true,
"outcome": "contradicted",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "example",
"credential_kind": "bachelor_degree",
"evidence_date": "2026-09-01",
"unresolved_source": "example",
"contradiction_detail": "example",
"service_level_due": "2026-09-01",
"claim_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"cached": true,
"created_at": "2026-09-01T09:00:00Z"
}
/v1/credentials/events#Replay what changed on a claim or verification
credentials.events.list
This reads back the events PROOF recorded for your organisation. Every event carries the record as it stood at that moment, so your screens can show how a verification reached its outcome and when a claim stopped being current.
With nothing in the query you get a page of events from across your records, oldest first, each under an evt_ identifier. Pass stream with a record's id and you get that record's history on its own.
Across records you get the order PROOF recorded events in, which is not always occurred_at order.
An event says what happened and never who did it. GET /v1/credentials/audit_entries is who, matched on request_id.
Open one event at GET /v1/credentials/events/{event_id}. Until something has happened in PROOF, this comes back empty and not refused.
Query parameters
stream
string
optionalThe id of the record whose history you want, such as a verification's from POST /v1/credentials/verifications or a claim's from POST /v1/credentials/claims. Leave it out to get events across every record, paged with starting_after.
after
integer
optionalThe sequence of the last event you handled on that stream, exclusive: you get the events after it. 0 reads from the start, and it needs stream beside it because a sequence counts within one stream.
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
The events themselves, each with sequence, so your handler tells a replay from a new event.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of StoredEvent
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of StoredEvent
id
string
requiredThe event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.
type
string
requiredWhat happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.
schema_version
integer · minimum 1
requiredThe version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.
org_id
string
requiredThe organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.
sequence
integer · minimum 0
requiredPer organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.
occurred_at
string · date-time
requiredWhen the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.
request_id
string
optionalThe request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.
livemode
boolean
requiredWhich realm the event happened in. False is the sandbox.
mocked
boolean
requiredWhether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.
source
string
requiredWhich part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.
anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligenceobject
always "event"
requiredAlways event. Tells you which kind of record you are looking at, so one handler can read any response.
stream
string
requiredThe id of the record this event is about, the same as data.id: a verification, claim, licence, re-attestation, appeal or document analysis. sequence counts within it; pass it as stream at GET /v1/credentials/events to replay.
data
object
requiredThe record the event is about, in the shape its type names: a verification, a claim, a licence, a re-attestation, an appeal or a document analysis. The same data a webhook delivery carries.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
const result = await api.credentialsEventsList({ stream: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after: 0, limit: 25 });// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result = api.credentials_events_list(stream='proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after=0, limit=25)import os
import requests
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$result = $api->credentialsEventsList(stream: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', after: 0, limit: 25);<?php
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.credentialsEventsList("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", 0, 25, null);// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
var result = api.CredentialsEventsList(stream: "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", after: 0, limit: 25);// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
result, _, err := client.PROOFAPI.CredentialsEventsList(ctx).Stream("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").After(0).Limit(25).Execute()// query parameters: stream (optional), after (optional), limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/events?stream=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&after=0&limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"type": "run.payslip.calculated",
"schema_version": 1,
"org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sequence": 0,
"occurred_at": "2026-09-01T09:00:00Z",
"livemode": false,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
],
"has_more": true
}
/v1/credentials/events/{event_id}#Open one event from a credential record
credentials.events.retrieve
This opens one event you already hold the identifier for. The identifier never changes, so an event you logged once is enough to show the same change again after the record it happened to has moved on.
Send the event's id in the path. You get the event whole: the record it belongs to, where it sits in that record's history, and data, the record itself as it stood at the time.
Carry stream and sequence to GET /v1/credentials/events as stream and after to read what followed. An identifier you hold no event for is refused.
Path parameters
event_id
string
requiredThe event's identifier, from the id of an event you listed at GET /v1/credentials/events or received on a webhook delivery. It starts with evt_.
Returns
The event, with occurred_at for when the change happened, so you can date it in your own records.
id
string
requiredThe event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.
type
string
requiredWhat happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.
schema_version
integer · minimum 1
requiredThe version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.
org_id
string
requiredThe organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.
sequence
integer · minimum 0
requiredPer organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.
occurred_at
string · date-time
requiredWhen the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.
request_id
string
optionalThe request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.
livemode
boolean
requiredWhich realm the event happened in. False is the sandbox.
mocked
boolean
requiredWhether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.
source
string
requiredWhich part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.
anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligenceobject
always "event"
requiredAlways event. Tells you which kind of record you are looking at, so one handler can read any response.
stream
string
requiredThe id of the record this event is about, the same as data.id: a verification, claim, licence, re-attestation, appeal or document analysis. sequence counts within it; pass it as stream at GET /v1/credentials/events to replay.
data
object
requiredThe record the event is about, in the shape its type names: a verification, a claim, a licence, a re-attestation, an appeal or a document analysis. The same data a webhook delivery carries.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsEventsRetrieve({ eventId: '{event_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_events_retrieve(event_id='{event_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsEventsRetrieve(event_id: '{event_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsEventsRetrieve("{event_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsEventsRetrieve(eventId: "{event_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsEventsRetrieve(ctx, "{event_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/events/%7Bevent_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "evt_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"type": "run.payslip.calculated",
"schema_version": 1,
"org_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"sequence": 0,
"occurred_at": "2026-09-01T09:00:00Z",
"livemode": false,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
/v1/credentials/audit_entries#Who acted on a person's credentials
credentials.audit_entries.list
This is who did what in PROOF, and how each attempt ended. A change in PROOF moves what it says a person can do, so your product can name who lapsed a claim or who recorded a contradiction.
You get a page of entries, newest first. Narrow it to one caller with actor_id, to one record with resource_id, or to the refusals alone with outcome.
An entry never says what changed or why it was refused. For what happened to the record itself, read GET /v1/credentials/events.
Read one in full at GET /v1/credentials/audit_entries/{audit_entry_id}, passing an entry's id as audit_entry_id.
Query parameters
action
string
optionalOnly attempts at one operation, as the method and route pattern an entry's action reads, such as POST /v1/credentials/verifications. Leave it out to get every operation.
actor_id
string
optionalOnly attempts by one caller: the id of the API key (key_…), OAuth client, signed-in person or staff account that made them, as actor_id reads on each entry. Leave it out to get every caller.
resource
string
optionalOnly attempts on one kind of record, as the collection segment of the route and as resource reads on an entry: verifications, claims, appeals and so on. Leave it out to get every kind.
resource_id
string
optionalOnly attempts on one record, by the identifier that was in the route and as resource_id reads on an entry, such as a verification's id from POST /v1/credentials/verifications. Leave it out to get every record.
outcome
string
optionalOnly attempts with one ending: succeeded (a 2xx answer), refused (a 4xx) or failed (a 5xx). Leave it out to get every ending; ask for refused to see every attempt that was turned away.
succeededrefusedfailedrecorded_after
string
optionalOnly entries whose at is after this moment, as an RFC 3339 timestamp in UTC. Exclusive: an entry at exactly this instant is left out, and without it there is no lower bound.
recorded_before
string
optionalOnly entries whose at is before this moment, as an RFC 3339 timestamp in UTC. Exclusive, so pair it with recorded_after to read one window; without it there is no upper bound.
limit
integer
optionalHow many records to return on one page, from 1 to 100, and 25 if you leave it out. When has_more is true, pass the last record's id as starting_after to get the next page.
starting_after
string
optionalThe id of the last record on the previous page of this same list. Leave it out to get the first page; when a page comes back with has_more true, send its last record's id here to get the page after it.
Returns
One entry per attempt, each with its request_id, so it matches a call in your own logs.
object
always "list"
requiredAlways list. Tells you which kind of record you are looking at, so one handler can read any response.
data
array of AuditEntry
requiredThe records on this page, in the order the list promises. Empty when nothing matched.
13 fields of AuditEntry
id
string
requiredThe entry's identifier, as GET /v1/credentials/audit_entries lists it. It starts with audit_entry_ and never changes; pass it as audit_entry_id to GET /v1/credentials/audit_entries/{audit_entry_id}, or as starting_after to page.
object
always "audit_entry"
requiredAlways audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm the action happened in. False is the sandbox.
mocked
boolean
requiredTrue when the route this attempt was aimed at is served by a mock. It describes the route, not the answer: a refused request and a replayed idempotent request are both attempts against a mocked route, and are recorded as such.
at
string · date-time
requiredWhen the attempt was made, as an RFC 3339 timestamp in UTC. The list is ordered by it, newest first.
request_id
string
requiredThe Droomwork-Request-Id of the call that made this attempt. It starts with req_; match it to your own logs, and quote it when you ask us about the call.
actor_type
string
requiredWhat kind of caller made the attempt: client for one of your API keys or OAuth clients, user for a signed-in person, service for Droomwork acting for you, staff for a Droomwork staff member.
actor_id
string
requiredWho made the attempt, as the id of whatever actor_type names: your API key (key_…) or OAuth client, the signed-in person, Droomwork acting for you, or the staff member. Pass it as actor_id to filter the list.
action
string
requiredWhat was attempted, as the method and the route pattern.
resource
string · nullable
optionalThe kind of record the attempt was aimed at, as the collection in the route, such as verifications or appeals. null when the route names none.
resource_id
string · nullable
optionalThe id of the record the attempt was aimed at, as it was in the route, such as a verification's from POST /v1/credentials/verifications. null for an attempt on a collection, such as creating or listing.
outcome
string
requiredHow the attempt ended: succeeded for a 2xx answer, refused for a 4xx, failed for a 5xx. A refusal is recorded like anything else; read status when you need the exact code.
succeededrefusedfailedstatus
integer
requiredThe HTTP status the caller was given, such as 201 or 403. Read it when outcome is too coarse: 403 and 404 are both refused.
has_more
boolean
requiredtrue when there are more records after this page. Pass the last record's id as starting_after to get the next page.
Other responses
Errors it can return
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
curl -X GET "https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
const result = await api.credentialsAuditEntriesList({ action: 'POST /v1/credentials/verifications', actorId: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'verifications', resourceId: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'succeeded', recordedAfter: '2026-01-01T00:00:00Z', recordedBefore: '2027-01-01T00:00:00Z', limit: 25 });// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
const response = await fetch('https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
result = api.credentials_audit_entries_list(action='POST /v1/credentials/verifications', actor_id='sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource='verifications', resource_id='proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome='succeeded', recorded_after='2026-01-01T00:00:00Z', recorded_before='2027-01-01T00:00:00Z', limit=25)import os
import requests
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
# query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
$result = $api->credentialsAuditEntriesList(action: 'POST /v1/credentials/verifications', actor_id: 'sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', resource: 'verifications', resource_id: 'proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z', outcome: 'succeeded', recorded_after: '2026-01-01T00:00:00Z', recorded_before: '2027-01-01T00:00:00Z', limit: 25);<?php
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var result = api.credentialsAuditEntriesList("POST /v1/credentials/verifications", "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "verifications", "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", "succeeded", "2026-01-01T00:00:00Z", "2027-01-01T00:00:00Z", 25, null);// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
var result = api.CredentialsAuditEntriesList(action: "POST /v1/credentials/verifications", actorId: "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", resource: "verifications", resourceId: "proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z", outcome: "succeeded", recordedAfter: "2026-01-01T00:00:00Z", recordedBefore: "2027-01-01T00:00:00Z", limit: 25);// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
result, _, err := client.PROOFAPI.CredentialsAuditEntriesList(ctx).Action("POST /v1/credentials/verifications").ActorId("sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Resource("verifications").ResourceId("proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z").Outcome("succeeded").RecordedAfter("2026-01-01T00:00:00Z").RecordedBefore("2027-01-01T00:00:00Z").Limit(25).Execute()// query parameters: action (optional), actor_id (optional), resource (optional), resource_id (optional), outcome (optional), recorded_after (optional), recorded_before (optional), limit (optional), starting_after (optional)
req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/audit_entries?action=POST%20%2Fv1%2Fcredentials%2Fverifications&actor_id=sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&resource=verifications&resource_id=proof_academic_verification_01J8XQ4M7K2N9P3R5T7V9W1Y3Z&outcome=succeeded&recorded_after=2026-01-01T00%3A00%3A00Z&recorded_before=2027-01-01T00%3A00%3A00Z&limit=25", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"object": "list",
"data": [
{
"id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "audit_entry",
"livemode": false,
"mocked": true,
"at": "2026-09-01T09:00:00Z",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"actor_type": "example",
"actor_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"action": "upload a current licence certificate",
"outcome": "succeeded",
"status": 1,
"resource": "verifications",
"resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"has_more": true
}
/v1/credentials/audit_entries/{audit_entry_id}#Trace one attempt back to its caller
credentials.audit_entries.retrieve
An audit entry is one attempt on your records, and this reads one back. It names the caller behind that attempt, so a lapsed claim can be traced to the call that did it.
Send the entry's id in the path. You get when the attempt was made, which caller made it, what it was aimed at, and how it ended.
An entry never says what changed or why it was refused. For what happened to the record itself, read GET /v1/credentials/events.
Where resource_id is set, open that record, such as a verification at GET /v1/credentials/verifications/{verification_id}. An identifier you hold no entry for is refused.
Path parameters
audit_entry_id
string
requiredThe entry's identifier, from the id of an entry you listed at GET /v1/credentials/audit_entries. It starts with audit_entry_.
Returns
The audit entry.
id
string
requiredThe entry's identifier, as GET /v1/credentials/audit_entries lists it. It starts with audit_entry_ and never changes; pass it as audit_entry_id to GET /v1/credentials/audit_entries/{audit_entry_id}, or as starting_after to page.
object
always "audit_entry"
requiredAlways audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.
livemode
boolean
requiredWhich realm the action happened in. False is the sandbox.
mocked
boolean
requiredTrue when the route this attempt was aimed at is served by a mock. It describes the route, not the answer: a refused request and a replayed idempotent request are both attempts against a mocked route, and are recorded as such.
at
string · date-time
requiredWhen the attempt was made, as an RFC 3339 timestamp in UTC. The list is ordered by it, newest first.
request_id
string
requiredThe Droomwork-Request-Id of the call that made this attempt. It starts with req_; match it to your own logs, and quote it when you ask us about the call.
actor_type
string
requiredWhat kind of caller made the attempt: client for one of your API keys or OAuth clients, user for a signed-in person, service for Droomwork acting for you, staff for a Droomwork staff member.
actor_id
string
requiredWho made the attempt, as the id of whatever actor_type names: your API key (key_…) or OAuth client, the signed-in person, Droomwork acting for you, or the staff member. Pass it as actor_id to filter the list.
action
string
requiredWhat was attempted, as the method and the route pattern.
resource
string · nullable
optionalThe kind of record the attempt was aimed at, as the collection in the route, such as verifications or appeals. null when the route names none.
resource_id
string · nullable
optionalThe id of the record the attempt was aimed at, as it was in the route, such as a verification's from POST /v1/credentials/verifications. null for an attempt on a collection, such as creating or listing.
outcome
string
requiredHow the attempt ended: succeeded for a 2xx answer, refused for a 4xx, failed for a 5xx. A refusal is recorded like anything else; read status when you need the exact code.
succeededrefusedfailedstatus
integer
requiredThe HTTP status the caller was given, such as 201 or 403. Read it when outcome is too coarse: 403 and 404 are both refused.
Other responses
Errors it can return
curl -X GET "https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D" \
-H "Droomwork-Api-Key: $DROOMWORK_API_KEY"import { Configuration, PROOFApi } from '@droomwork/sdk';
const api = new PROOFApi(new Configuration({ basePath: 'https://sandbox.droomwork.io', accessToken: process.env.DROOMWORK_API_KEY }));
const result = await api.credentialsAuditEntriesRetrieve({ auditEntryId: '{audit_entry_id}' });const response = await fetch('https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D', {
method: 'GET',
headers: {
'Droomwork-Api-Key': process.env.DROOMWORK_API_KEY,
},
});
const result = await response.json();import os
import droomwork
config = droomwork.Configuration(access_token=os.environ['DROOMWORK_API_KEY'])
config.host = 'https://sandbox.droomwork.io'
client = droomwork.ApiClient(config)
api = droomwork.PROOFApi(client)
result = api.credentials_audit_entries_retrieve(audit_entry_id='{audit_entry_id}')import os
import requests
response = requests.request(
'GET',
'https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D',
headers={
'Droomwork-Api-Key': os.environ['DROOMWORK_API_KEY'],
},
)
result = response.json()<?php
require_once __DIR__ . '/vendor/autoload.php';
$config = DroomworkSdk\Configuration::getDefaultConfiguration()
->setHost('https://sandbox.droomwork.io')
->setAccessToken(getenv('DROOMWORK_API_KEY'));
$api = new DroomworkSdk\Api\PROOFApi(new GuzzleHttp\Client(), $config);
$result = $api->credentialsAuditEntriesRetrieve(audit_entry_id: '{audit_entry_id}');<?php
$ch = curl_init('https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Droomwork-Api-Key: ' . getenv('DROOMWORK_API_KEY'),
],
]);
$result = json_decode(curl_exec($ch), true);import com.droomwork.sdk.ApiClient;
import com.droomwork.sdk.Configuration;
import com.droomwork.sdk.api.ProofApi;
ApiClient client = Configuration.getDefaultApiClient();
client.setBasePath("https://sandbox.droomwork.io");
client.setAccessToken(System.getenv("DROOMWORK_API_KEY"));
ProofApi api = new ProofApi(client);
var result = api.credentialsAuditEntriesRetrieve("{audit_entry_id}");var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D"))
.header("Droomwork-Api-Key", System.getenv("DROOMWORK_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();using Droomwork.Sdk.Api;
using Droomwork.Sdk.Client;
var config = new Configuration { BasePath = "https://sandbox.droomwork.io" };
config.AccessToken = Environment.GetEnvironmentVariable("DROOMWORK_API_KEY");
var api = new PROOFApi(config);
var result = api.CredentialsAuditEntriesRetrieve(auditEntryId: "{audit_entry_id}");using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D");
request.Headers.Add("Droomwork-Api-Key", Environment.GetEnvironmentVariable("DROOMWORK_API_KEY"));
var response = await client.SendAsync(request);
var result = await response.Content.ReadAsStringAsync();import droomwork "github.com/fenibofubara/droomwork-sdk-go"
ctx := context.WithValue(context.Background(), droomwork.ContextAPIKeys, map[string]droomwork.APIKey{
"apiKey": {Key: os.Getenv("DROOMWORK_API_KEY")},
})
cfg := droomwork.NewConfiguration()
cfg.Servers = droomwork.ServerConfigurations{{URL: "https://sandbox.droomwork.io"}}
client := droomwork.NewAPIClient(cfg)
result, _, err := client.PROOFAPI.CredentialsAuditEntriesRetrieve(ctx, "{audit_entry_id}").Execute()req, _ := http.NewRequest("GET", "https://sandbox.droomwork.io/v1/credentials/audit_entries/%7Baudit_entry_id%7D", nil)
req.Header.Set("Droomwork-Api-Key", os.Getenv("DROOMWORK_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
{
"id": "audit_entry_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "audit_entry",
"livemode": false,
"mocked": true,
"at": "2026-09-01T09:00:00Z",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"actor_type": "example",
"actor_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"action": "upload a current licence certificate",
"outcome": "succeeded",
"status": 1,
"resource": "verifications",
"resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}