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 quietly extended. 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#List verifications
credentials.verifications.list
Your organisation's verifications, newest first.
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
A page of verifications.
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": true,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "Rivers State Internal Revenue Service",
"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
Check a claimed qualification with the issuer that granted it.
The subject needs a live, unrevoked ANCHOR Passport, or an identity you supply under an attestation. Which one was relied on is recorded on the claim.
Where the issuer has no machine interface you get pending back, with the service level for the assisted path. Pending is not a failure and not a pass.
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 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.
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": true,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "Rivers State Internal Revenue Service",
"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}#Retrieve a verification
credentials.verifications.retrieve
The outcome with its source, its method and its date. An unverified outcome names the register that didn't answer.
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 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.
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": true,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "Rivers State Internal Revenue Service",
"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#List issuers and how each is reached
credentials.issuers.list
Universities and polytechnics, the national youth service scheme, professional and regulatory bodies, and examinations boards.
Each tells you how it's reached and how long its evidence stays valid. An issuer reached by an assisted path also tells you its service level, so you know what pending means before you see it.
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
A page of issuers.
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": "Rivers State Internal Revenue Service",
"kind": "university",
"method": "register_api",
"service_level_days": 1,
"evidence_validity_days": 1,
"reachable": true
}
],
"has_more": true
}
/v1/credentials/issuers/{issuer_id}#Retrieve an issuer
credentials.issuers.retrieve
The issuer, with how it's reached, its service level and how long its evidence stays valid.
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
The 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.
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": "Rivers State Internal Revenue Service",
"kind": "university",
"method": "register_api",
"service_level_days": 1,
"evidence_validity_days": 1,
"reachable": true
}
/v1/credentials/frameworks#List competency frameworks
credentials.frameworks.list
Each supported occupation code, broken into claims you can assess one at a time and mapped to the national vocational qualification framework.
Frameworks are versioned. A claim made under one version still means what it meant when it was made.
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
A page of frameworks.
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": "no_payee_destination",
"description": "example",
"nvqf_level": 1
}
],
"occupation_name": "Rivers State Internal Revenue Service",
"nvqf_level": 1
}
],
"has_more": true
}
/v1/credentials/frameworks/{framework_id}#Retrieve a competency framework
credentials.frameworks.retrieve
The occupation, broken into claims you can assess independently.
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
The 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.
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": "no_payee_destination",
"description": "example",
"nvqf_level": 1
}
],
"occupation_name": "Rivers State Internal Revenue Service",
"nvqf_level": 1
}
/v1/credentials/document_analyses#Analyse a document for signs of forgery
credentials.document_analyses.create
Send a document and get five factors scored separately: the template, the seal, the typography, the serial structure and whether the issuance is plausible at all.
You get five scores rather than one number, so you can see which factor raised the concern and argue with it.
A confirmed forgery is escalated to ANCHOR, which decides whether to blocklist.
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 analysis, with a score per factor.
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": true,
"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}#Retrieve a document analysis
credentials.document_analyses.retrieve
The five factor scores and the overall finding. You don't get the document back, and you don't get the reasoning behind any score.
Path parameters
document_analysis_id
string
requiredThe analysis's identifier: the id returned by POST /v1/credentials/document_analyses, starting proof_academic_verification_.
Returns
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": true,
"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#List Skill Wallets you may read
credentials.wallets.list
The Wallets you may read. Use it to find what's already established about a subject before you pay to verify it again.
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
A page of Wallets.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claims": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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#Create a skill wallet
credentials.wallets.create
Open the wallet a subject holds their claims in. It's the subject's, not yours: a disclosure is how you see inside it, and a disclosure expires.
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
The wallet.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claims": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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}#Retrieve a Skill Wallet
credentials.wallets.retrieve
Everything established about one person, with the decay state of each claim.
A lapsed claim still appears, marked lapsed and downgraded. The person once held it, and that is usually the question you're asking.
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
The Skill Wallet.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claims": [
{
"id": "proof_academic_claim_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "competence_claim",
"livemode": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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#List competence claims
credentials.claims.list
The claims you may see, through your own verification or a consented disclosure.
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.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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 succeeds, carrying the evidence class that produced it. Create one directly to record a competence established elsewhere. Its evidence class says how far you can rely on it.
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 claim.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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}#Retrieve a competence claim
credentials.claims.retrieve
The claim with its level, evidence class, evidence date, issuing method and how far it has decayed.
Read the evidence class carefully. Classes are ordered by how directly they observe the work, not by how formal they look, so a demonstration outranks a certificate.
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
The claim.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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#List credential disclosures
credentials.disclosures.list
Which Wallets you may currently read, and until when.
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": true,
"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
Where a claim has already been verified for another organisation, take it as a consented disclosure instead of paying to verify it again.
The subject is verified once and carries the result. You don't pay the same register for the same answer.
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.
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": true,
"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 credential disclosure
credentials.disclosures.revoke
Ends your access immediately. The subject may do this themselves.
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": true,
"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/licences#List statutory and professional licences
credentials.licences.list
Each licence carries its issue and expiry dates. When one lapses the Wallet entry expires on its own, and you're told if you hold a live disclosure.
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
A page of licences.
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}#Retrieve a licence
credentials.licences.retrieve
The licence, and whether the law requires it for the occupation it belongs to.
Where it's required, its absence is a hard block. Read statutory_block as a gate on its own, never as one input among several. No other evidence substitutes for a licence the law demands.
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.
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
Refresh a claim that's ageing toward expiry. The worker gets a notice with the specific path to follow, not only a warning that something is about to 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
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}#Retrieve a re-attestation
credentials.reattestations.retrieve
Where the re-attestation stands and what the worker still has to do.
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.
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#Appeal a verification outcome
credentials.appeals.create
A subject may challenge a result. File it here with the subject's reference. An appeal comes with a service level, so you get a date by which it will be answered.
A successful appeal restores standing, and everyone holding a live disclosure is told, the same as they would be of a 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
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}#Retrieve an appeal
credentials.appeals.retrieve
Where the appeal stands, its outcome and what changed as a result.
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.
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, what you have, and what is missing
credentials.readiness.retrieve
Before any verification runs, the subject must be anchored. This tells you whether that comes from ANCHOR or from an identity you supply, and which issuers you can currently reach.
Returns
The readiness report.
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": "anchor",
"mode": "integrated",
"ready": true,
"rows": [
{
"fact": "anchored_subject",
"owner": "anchor",
"required": "attested",
"held": "attested",
"satisfied": true,
"contract_row": "6.1.13",
"source": "integrated",
"missing_because": "not_supplied"
}
],
"always_enforced": [
"example"
],
"issuers_unreachable": [
"example"
],
"subjects_without_passport": 1
}
/v1/credentials/claims/{claim_id}/lapse#Lapse a claim
credentials.claims.lapse
Record that a claim's evidence is no longer current. That's different from the claim having been wrong: the history stays and the claim stops counting. Re-attestation is the way back.
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 lapsed claim.
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": true,
"mocked": true,
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"claim_code": "no_payee_destination",
"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#Verify a credential
credentials.verifications.verify
Record that the issuer confirmed the credential. You get a competence claim against the framework the credential maps to.
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 in its new state.
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": true,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "Rivers State Internal Revenue Service",
"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#Mark a credential unverified
credentials.verifications.mark_unverified
Record that the issuer couldn't answer. That is not the same as the credential being false, and the source that couldn't answer is named.
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 in its new state.
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": true,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "Rivers State Internal Revenue Service",
"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#Contradict a credential
credentials.verifications.contradict
Record that the issuer says the credential is not theirs. This is the one outcome that is a finding against the subject. It's separate from unverified and is never reached by a timeout.
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 in its new state.
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": true,
"mocked": true,
"outcome": "pending",
"subject_ref": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"issuer_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"method": "register_api",
"passport_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"identity_assurance": "attested",
"issuer_name": "Rivers State Internal Revenue Service",
"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#List events
credentials.events.list
Everything PROOF has recorded for your organisation, append only, oldest first. Every event here is one the webhook catalogue declares, so the code that handles a delivery handles a replay too.
Two ways to read it. Pass stream with after to replay one stream from the sequence you last handled. That's exact and needs no cursor: the sequence counts per organisation and per stream, so it's the only ordering that means anything. Without stream, you get events across streams in the order they were recorded, paged with starting_after.
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
A page of events.
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": true,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
],
"has_more": true
}
/v1/credentials/events/{event_id}#Retrieve an event
credentials.events.retrieve
One event. An identifier belonging to another organisation comes back not found rather than refused, so a refusal never confirms it exists.
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.
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": true,
"mocked": true,
"source": "anchor",
"request_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z",
"object": "event",
"stream": "example",
"data": {}
}
/v1/credentials/audit_entries#List audit entries
credentials.audit_entries.list
Who did what, newest first. One row per attempt, not per success. Refusals are recorded.
A read that succeeded is not recorded.
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
A page of audit entries.
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": true,
"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": "example",
"resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}
],
"has_more": true
}
/v1/credentials/audit_entries/{audit_entry_id}#Retrieve an audit entry
credentials.audit_entries.retrieve
One entry. An identifier belonging to another organisation comes back not found rather than refused, so a refusal never confirms it exists.
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": true,
"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": "example",
"resource_id": "sub_01J8XQ4M7K2N9P3R5T7V9W1Y3Z"
}