DDroomwork Developers

Version 1.0.0

Droomwork PROOF ACADEMIC

Credential verification and competence claims for Nigeria.

Base URLhttps://sandbox.droomwork.io
AuthenticationDroomwork-Api-Key: dw_test_… or Authorization: Bearer dw_test_…
Events it sends8 webhooks →
Signed in: requests you run on this page use a key made for your account. Your keys →

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?

GET/v1/credentials/verifications#

List verifications

credentials.verifications.list

Your organisation's verifications, newest first.

Query parameters

limit integer optional

How 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 optional

The 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 optional

Restricts 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.

pendingverifiedunverifiedcontradicted
subject_ref string optional

Restricts 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Verification required

The records on this page, in the order the list promises. Empty when nothing matched.

19 fields of Verification
id string required

The 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" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

Four 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.

pendingverifiedunverifiedcontradicted
subject_ref string required

The 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 optional

The 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 optional

How 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.

attestedverified
issuer_id string required

The 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 optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The 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 optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
POST/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 required

A 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 required

An 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 optional

The 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 required

The 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 required

What kind of credential you are checking, such as bachelor_degree. It comes back unchanged on the verification.

claimed_award string optional

The 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 optional

The year the subject says the credential was awarded, as a whole number. Omit it if you don't have it.

document_analysis_id string optional

Links 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 required

The 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" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

Four 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.

pendingverifiedunverifiedcontradicted
subject_ref string required

The 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 optional

The 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 optional

How 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.

attestedverified
issuer_id string required

The 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 optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The 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 optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 required

The 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 required

The 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" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

Four 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.

pendingverifiedunverifiedcontradicted
subject_ref string required

The 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 optional

The 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 optional

How 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.

attestedverified
issuer_id string required

The 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 optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The 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 optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 optional

How 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 optional

The 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 optional

Restricts 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_board
method string optional

Restricts 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_manual

Returns

A page of issuers.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Issuer required

The records on this page, in the order the list promises. Empty when nothing matched.

8 fields of Issuer
id string required

The 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" required

Always issuer. Tells you which kind of record you are looking at, so one handler can read any response.

name string required

The 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 required
universitypolytechnicnational_youth_serviceprofessional_bodyregulatory_bodyexaminations_board
method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
service_level_days integer · nullable optional

Published for an assisted path. How long pending should last.

evidence_validity_days integer optional

How long evidence from this issuer stays usable before re-querying.

reachable boolean optional

Whether 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
GET/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 required

The 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 required

The 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" required

Always issuer. Tells you which kind of record you are looking at, so one handler can read any response.

name string required

The 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 required
universitypolytechnicnational_youth_serviceprofessional_bodyregulatory_bodyexaminations_board
method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
service_level_days integer · nullable optional

Published for an assisted path. How long pending should last.

evidence_validity_days integer optional

How long evidence from this issuer stays usable before re-querying.

reachable boolean optional

Whether 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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
}
GET/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 optional

How 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 optional

Restricts 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Framework required

The records on this page, in the order the list promises. Empty when nothing matched.

7 fields of Framework
id string required

The 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" required

Always competency_framework. Tells you which kind of record you are looking at, so one handler can read any response.

occupation_code string required

The 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 optional

The occupation in words, for showing beside the code. Match on occupation_code, not on this.

version string required

Which 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 optional

The mapped level on the national vocational qualification framework.

claims array of object required

Independently assessable. Someone can hold three of five.

3 fields
code string required

The 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 required

What the claim says the person can do, in words. Show it; match on code.

nvqf_level integer · nullable optional

The level this claim maps to on the national vocational qualification framework. null where the claim has no mapped level.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
GET/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 required

The 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 required

The 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" required

Always competency_framework. Tells you which kind of record you are looking at, so one handler can read any response.

occupation_code string required

The 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 optional

The occupation in words, for showing beside the code. Match on occupation_code, not on this.

version string required

Which 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 optional

The mapped level on the national vocational qualification framework.

claims array of object required

Independently assessable. Someone can hold three of five.

3 fields
code string required

The 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 required

What the claim says the person can do, in words. Show it; match on code.

nvqf_level integer · nullable optional

The level this claim maps to on the national vocational qualification framework. null where the claim has no mapped level.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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
}
POST/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 required

A 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 required

An 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 required

Write only. The document is analysed and never returned, because a certificate belongs to the person it names.

credential_kind string required

What 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 required

The 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" required

Always document_analysis. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 optional

The 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 required

The 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_forgery
factors array of object required

One 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 required

Five factors, scored separately so a concern can be pointed at.

templatesealtypographyserial_structureissuance_plausibility
score number · minimum 0 · maximum 1 required

Higher means more consistent with a genuine document.

note string required

A 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 optional

True on a confirmed forgery, which is escalated to ANCHOR to decide whether to blocklist.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 required

The analysis's identifier: the id returned by POST /v1/credentials/document_analyses, starting proof_academic_verification_.

Returns

The analysis.

id string required

The 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" required

Always document_analysis. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 optional

The 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 required

The 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_forgery
factors array of object required

One 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 required

Five factors, scored separately so a concern can be pointed at.

templatesealtypographyserial_structureissuance_plausibility
score number · minimum 0 · maximum 1 required

Higher means more consistent with a genuine document.

note string required

A 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 optional

True on a confirmed forgery, which is escalated to ANCHOR to decide whether to blocklist.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 optional

How 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 optional

The 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 optional

Restricts 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of SkillWallet required

The records on this page, in the order the list promises. Empty when nothing matched.

8 fields of SkillWallet
id string required

The 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" required

Always skill_wallet. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Lapsed claims appear, marked lapsed. Nothing is removed.

16 fields of CompetenceClaim
id string required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

licences array of Licence optional

The 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 required

This 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" required

Always licence. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

What 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 optional

The 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 optional

Masked. The number itself belongs to the holder.

status string required
validexpiringexpiredrevoked
issued_on string · date optional

The day the issuer granted the licence, as a YYYY-MM-DD date.

expires_on string · date optional

The 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 optional

Occupations where the law requires this licence.

statutory_block boolean required

True 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 optional

Occupations 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 required

The 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 required

Why 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_revoked
has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
POST/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 required

A 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 optional

The 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 required

The 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" required

Always skill_wallet. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Lapsed claims appear, marked lapsed. Nothing is removed.

16 fields of CompetenceClaim
id string required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

licences array of Licence optional

The 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 required

This 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" required

Always licence. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

What 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 optional

The 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 optional

Masked. The number itself belongs to the holder.

status string required
validexpiringexpiredrevoked
issued_on string · date optional

The day the issuer granted the licence, as a YYYY-MM-DD date.

expires_on string · date optional

The 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 optional

Occupations where the law requires this licence.

statutory_block boolean required

True 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 optional

Occupations 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 required

The 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 required

Why 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_revoked

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
    }
  ]
}
GET/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 required

The 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 required

The 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" required

Always skill_wallet. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Lapsed claims appear, marked lapsed. Nothing is removed.

16 fields of CompetenceClaim
id string required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

licences array of Licence optional

The 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 required

This 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" required

Always licence. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

What 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 optional

The 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 optional

Masked. The number itself belongs to the holder.

status string required
validexpiringexpiredrevoked
issued_on string · date optional

The day the issuer granted the licence, as a YYYY-MM-DD date.

expires_on string · date optional

The 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 optional

Occupations where the law requires this licence.

statutory_block boolean required

True 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 optional

Occupations 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 required

The 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 required

Why 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_revoked

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
    }
  ]
}
GET/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 optional

How 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 optional

The 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 optional

Restricts 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 optional

Restricts 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.

currentageingstalelapsed
occupation_code string optional

Restricts 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of CompetenceClaim required

The records on this page, in the order the list promises. Empty when nothing matched.

16 fields of CompetenceClaim
id string required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
POST/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 required

A 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 optional

The 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 required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 required

The 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 required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/v1/credentials/disclosures#

List credential disclosures

credentials.disclosures.list

Which Wallets you may currently read, and until when.

Query parameters

limit integer optional

How 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 optional

The 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of CredentialDisclosure required

The records on this page, in the order the list promises. Empty when nothing matched.

11 fields of CredentialDisclosure
id string required

This 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" required

Always credential_disclosure. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 optional

The 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 optional

The 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 optional

The 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 required

active 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.

activerevokedexpired
reused_existing_evidence boolean optional

True when nothing was re-verified and nothing was re-charged.

expires_at string · date-time required

When your access ends, as an RFC 3339 timestamp in UTC; after it, status is expired. Every disclosure has one.

has_more boolean required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
POST/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 required

A 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 required

The 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 optional

The 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 required

The 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 required

When your access should end, as an RFC 3339 timestamp in UTC. Required: there is no open ended disclosure.

Returns

The disclosure.

id string required

This 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" required

Always credential_disclosure. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 optional

The 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 optional

The 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 optional

The 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 required

active 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.

activerevokedexpired
reused_existing_evidence boolean optional

True when nothing was re-verified and nothing was re-charged.

expires_at string · date-time required

When your access ends, as an RFC 3339 timestamp in UTC; after it, status is expired. Every disclosure has one.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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
}
POST/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 required

The 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 required

A 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 required

This 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" required

Always credential_disclosure. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 optional

The 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 optional

The 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 optional

The 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 required

active 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.

activerevokedexpired
reused_existing_evidence boolean optional

True when nothing was re-verified and nothing was re-charged.

expires_at string · date-time required

When your access ends, as an RFC 3339 timestamp in UTC; after it, status is expired. Every disclosure has one.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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
}
GET/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 optional

How 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 optional

The 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 optional

Restricts 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 optional

Restricts 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.

validexpiringexpiredrevoked

Returns

A page of licences.

object always "list" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of Licence required

The records on this page, in the order the list promises. Empty when nothing matched.

11 fields of Licence
id string required

This 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" required

Always licence. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

What 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 optional

The 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 optional

Masked. The number itself belongs to the holder.

status string required
validexpiringexpiredrevoked
issued_on string · date optional

The day the issuer granted the licence, as a YYYY-MM-DD date.

expires_on string · date optional

The 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 optional

Occupations where the law requires this licence.

statutory_block boolean required

True 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
GET/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 required

The 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 required

This 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" required

Always licence. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

What 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 optional

The 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 optional

Masked. The number itself belongs to the holder.

status string required
validexpiringexpiredrevoked
issued_on string · date optional

The day the issuer granted the licence, as a YYYY-MM-DD date.

expires_on string · date optional

The 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 optional

Occupations where the law requires this licence.

statutory_block boolean required

True 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
  ]
}
POST/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 required

A 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 required

The 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 required

This 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" required

Always reattestation. Tells you which kind of record you are looking at, so one handler can read any response.

claim_id string required

The 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 optional

The 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 required

Where 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_progresscompletedexpired
path object required

What the worker actually has to do, rather than a warning that something is about to lapse.

3 fields
action string required

What the worker has to do, in words you can show them, such as upload a current licence certificate.

url string · uri required

The link the worker follows to do it. The notice carries it, and you can show it to them yourself as well.

estimated_minutes integer optional

How long the step should take the worker, in minutes. Absent when there is no estimate.

notice_sent_at string · date-time optional

When the worker was sent the notice carrying this path, as an RFC 3339 timestamp in UTC.

claim_expires_at string · date optional

The 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 optional

The 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

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 required

The 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 required

This 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" required

Always reattestation. Tells you which kind of record you are looking at, so one handler can read any response.

claim_id string required

The 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 optional

The 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 required

Where 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_progresscompletedexpired
path object required

What the worker actually has to do, rather than a warning that something is about to lapse.

3 fields
action string required

What the worker has to do, in words you can show them, such as upload a current licence certificate.

url string · uri required

The link the worker follows to do it. The notice carries it, and you can show it to them yourself as well.

estimated_minutes integer optional

How long the step should take the worker, in minutes. Absent when there is no estimate.

notice_sent_at string · date-time optional

When the worker was sent the notice carrying this path, as an RFC 3339 timestamp in UTC.

claim_expires_at string · date optional

The 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 optional

The 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
POST/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 required

A 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 required

The 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 required

The result being challenged, named by its kind and id. One appeal challenges one result.

2 fields
kind string required

Which 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_analysisclaimlicence
id string required

The 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 required

Why 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 optional

A 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 required

The 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" required

Always appeal. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

The result under appeal, by its kind and id, as you named it when you filed.

2 fields
kind string required

Which 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_analysisclaimlicence
id string required

The 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 optional

Why the subject says the result is wrong, as you stated it when you filed.

status string required

Where 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_reviewupheldrejected
due_by string · date required

The published service level. An appeal has a date, not an intention.

outcome_detail string · nullable optional

The reasons given with the decision, in plain words. null while there is no decision to explain.

restored_claim_ids array of string optional

Set when the appeal was upheld. These propagate to every live disclosure.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
  ]
}
GET/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 required

The 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 required

The 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" required

Always appeal. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The 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 required

The result under appeal, by its kind and id, as you named it when you filed.

2 fields
kind string required

Which 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_analysisclaimlicence
id string required

The 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 optional

Why the subject says the result is wrong, as you stated it when you filed.

status string required

Where 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_reviewupheldrejected
due_by string · date required

The published service level. An appeal has a date, not an intention.

outcome_detail string · nullable optional

The reasons given with the decision, in plain words. null while there is no decision to explain.

restored_claim_ids array of string optional

Set when the appeal was upheld. These propagate to every live disclosure.

Other responses

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
  ]
}
GET/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" required

Always readiness_report. Tells you which kind of record you are looking at, so one handler can read any response.

module string required

The 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).

anchorproofrailflowmatchrunremitroute
mode string required

integrated 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.

integratedstandalone
ready boolean required

true 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 required

One 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 required

Named for what it is, not for who supplies it.

owner string required

The sibling module that owns this fact when running integrated.

contract_row string optional
required string required

How 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.

attestedverified
held one of required
Assuranceor
source string · nullable optional
integratedstandalonenull
satisfied boolean required
missing_because string · nullable optional
not_suppliedassurance_too_lownull
always_enforced array of string optional

Gates 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 optional

The 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 optional

How 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

401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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
}
POST/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 required

The 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 required

A 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 required

The 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" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

The 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 required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The 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 required

How 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 required

Ordered 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_demonstrated
evidence_date string · date required

The 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 optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The 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 optional

Set when a re-attestation replaced this claim. The old one stays.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
POST/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 required

The 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 required

A 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 required

The 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" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

Four 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.

pendingverifiedunverifiedcontradicted
subject_ref string required

The 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 optional

The 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 optional

How 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.

attestedverified
issuer_id string required

The 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 optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The 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 optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
POST/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 required

The 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 required

A 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 required

The 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" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

Four 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.

pendingverifiedunverifiedcontradicted
subject_ref string required

The 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 optional

The 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 optional

How 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.

attestedverified
issuer_id string required

The 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 optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The 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 optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
POST/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 required

The 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 required

A 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 required

The 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" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where 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 required

Four 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.

pendingverifiedunverifiedcontradicted
subject_ref string required

The 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 optional

The 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 optional

How 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.

attestedverified
issuer_id string required

The 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 optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The 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 optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.
409The record is not in a state that allows this.
422A required fact is missing. Call the readiness endpoint to see what.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}
GET/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 optional

The 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 optional

The 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 optional

How 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 optional

The 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of StoredEvent required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of StoredEvent
id string required

The 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 required

What 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 required

The 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 required

The 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 required

Per 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 required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The 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 required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether 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 required

Which 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.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
object always "event" required

Always event. Tells you which kind of record you are looking at, so one handler can read any response.

stream string required

The 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 required

The 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
GET/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 required

The 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 required

The 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 required

What 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 required

The 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 required

The 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 required

Per 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 required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The 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 required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether 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 required

Which 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.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
object always "event" required

Always event. Tells you which kind of record you are looking at, so one handler can read any response.

stream string required

The 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 required

The 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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": {}
}
GET/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 optional

Only 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 optional

Only 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 optional

Only 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 optional

Only 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 optional

Only 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.

succeededrefusedfailed
recorded_after string optional

Only 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 optional

Only 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 optional

How 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 optional

The 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" required

Always list. Tells you which kind of record you are looking at, so one handler can read any response.

data array of AuditEntry required

The records on this page, in the order the list promises. Empty when nothing matched.

13 fields of AuditEntry
id string required

The 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" required

Always audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm the action happened in. False is the sandbox.

mocked boolean required

True 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 required

When the attempt was made, as an RFC 3339 timestamp in UTC. The list is ordered by it, newest first.

request_id string required

The 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 required

What 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 required

Who 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 required

What was attempted, as the method and the route pattern.

resource string · nullable optional

The 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 optional

The 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 required

How 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.

succeededrefusedfailed
status integer required

The 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 required

true when there are more records after this page. Pass the last record's id as starting_after to get the next page.

Other responses

400The request could not be read, or a value was refused.
401No valid credential was presented.
403The credential does not carry the required scope.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
# 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)
Response
{
  "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
}
GET/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 required

The 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 required

The 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" required

Always audit_entry. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm the action happened in. False is the sandbox.

mocked boolean required

True 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 required

When the attempt was made, as an RFC 3339 timestamp in UTC. The list is ordered by it, newest first.

request_id string required

The 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 required

What 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 required

Who 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 required

What was attempted, as the method and the route pattern.

resource string · nullable optional

The 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 optional

The 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 required

How 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.

succeededrefusedfailed
status integer required

The 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

401No valid credential was presented.
403The credential does not carry the required scope.
404No record with that identifier.

Errors it can return

Sends against https://sandbox.droomwork.io
Request
which?
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)
Response
{
  "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"
}