Certificates API
Verify any certificate without a key, issue and revoke with a team ov_ key over REST, and verify from an agent with the MCP tool verify_credential.
Three interfaces, all on https://osintverse.com (not the SearchIn API host):
| Interface | Auth | What it does |
|---|---|---|
| Public verification | None | Read a certificate's signed record, status, and how to check it. |
| REST issuing | Team ov_ key with Certificates access | Issue, list, read, and revoke certificates for the key's issuer. |
MCP verify_credential | Any MCP connection | The public record, for agents. |
Public verification
GET /api/certificates/verify/{id}
GET /certificates/verify/{id}.json (same response)
GET /certificates/verify/{id}.md (Markdown twin)
GET /api/certificates/keys (every signing key, past and present)
GET /api/certificates/{id}/openbadge.json (Open Badges 3.0 export, eddsa-jcs-2022 proof)
GET /.well-known/did.json (did:web document naming the export's signing keys)The Open Badges export is a W3C Verifiable Credential built from the signed record on request; valid and expired certificates export, revoked and superseded ones answer 410. Details and how to verify the proof are on How verification works.
No account, no key. Rate-limited per IP; 429 carries Retry-After. The response never includes the recipient's email. An unknown id answers 404 with { "message", "code": "not_found" }. Ids are normalised, so ovc7k3qm8px2dla and OVC-7K3Q-M8PX-2DLA are the same certificate; issuer-prefixed ids such as OSINTCOL1-48213967 work the same way.
curl -s https://osintverse.com/api/certificates/verify/OVC-7K3Q-M8PX-2DLA | jq '{status, title, issuer, recipient, signature}'The response shape and the verification steps are on How verification works. Pages that the recipient has not opted into indexing carry X-Robots-Tag: noindex.
REST issuing
Authentication
Create a team API key on the team's API keys page with access level Certificates (issue, read, and revoke certificates; quotes and providers readable; never charges) or Full access. Personal keys cannot issue: certificates belong to a team's issuer. The key's creator must still be a member whose role can issue (owner, admin, member, or analyst).
Send the key as a bearer token or in x-api-key:
Authorization: Bearer ov_your_key
x-api-key: ov_your_keyEvery call counts once toward the key's daily request limit. Errors are { "message": "…", "code": "…" }:
| Status | Code | Meaning |
|---|---|---|
401 | invalid_key | Missing, unknown, expired, or revoked key. |
403 | team_key_required | A personal key. Create the key on the team's API keys page. |
403 | capability_missing | The key's access level does not include certificates. |
403 | forbidden | The key creator's team role cannot issue. |
403 | issuer_blocked, batch_limit, monthly_cap | Trust-level limits (see Issuing). |
404 | no_issuer | The key's team has no issuer yet. Create one in the issuer console. |
404 | not_found | Unknown type or certificate for this issuer. |
409 | all_duplicates, type_archived, bad_status | Nothing to do, or the state does not allow the action. |
429 | rate_limited | The key's daily request limit. Retry-After is set. |
503 | certificates_disabled, signing_unavailable | Issuing is paused, or the signing key is not configured. Verification keeps working. |
POST /api/certificates/issue
Issues one certificate or a batch from a type you own. Same validation and caps as the console; the batch source is recorded as api.
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | Yes | The credential type's slug or id. |
recipients | object[] | One of recipients or recipient | Batch. The size limit depends on the issuer's trust level. |
recipient | object | A single recipient (same shape as a recipients item). | |
personal_message | string | No | A line for every email in the batch; a recipient's message overrides it. |
allow_duplicates | boolean | No | Issue even to recipients who already hold a valid certificate of this type. Default false: they are skipped and counted in skipped. |
Each recipient:
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | |
email | string | Yes | Lower-cased. |
handle | string | No | Without the @. Needed for handle display modes. |
issued_at | string | No | YYYY-MM-DD; default today. |
expires_at | string | No | YYYY-MM-DD, after issued_at; default from the type. |
fields | object | Per type | { "score": "92" }; values are strings and are validated by the type's field kinds. |
message | string | No | Personal line for this recipient's email. |
curl -X POST https://osintverse.com/api/certificates/issue \
-H "Authorization: Bearer ov_your_key" \
-H "Content-Type: application/json" \
-d '{
"type": "osint-fundamentals-completion",
"recipient": { "name": "Jane Doe", "email": "jane@example.com", "handle": "janedoe", "fields": { "score": "92" } },
"personal_message": "Great work on the final exercise."
}'{
"batch_id": "6f1c…",
"source": "api",
"issued": 1,
"skipped": 0,
"certificates": [
{
"id": "OVC-7K3Q-M8PX-2DLA",
"recipient_name": "Jane Doe",
"recipient_email": "jane@example.com",
"verify_url": "https://osintverse.com/certificates/verify/OVC-7K3Q-M8PX-2DLA",
"json_url": "https://osintverse.com/certificates/verify/OVC-7K3Q-M8PX-2DLA.json"
}
]
}Rows that fail validation fail the whole call with 400 and code: invalid_rows; the message names the first few problems. Emails go out after the response; read each certificate's email.status later.
No idempotency key yet
A retried call issues again unless the recipients already hold the certificate (the default duplicate check). Keep allow_duplicates off for retries.
GET /api/certificates/issued
Lists the issuer's certificates, newest first, with keyset paging.
| Query | Notes |
|---|---|
q | Matches name, email, handle, or id. |
type | Type slug or id. |
status | valid, revoked, or superseded. |
cursor | next_cursor from the previous page. |
limit | Rows per page, up to the list page limit. |
{ "items": [ { "id": "OVC-…", "…": "…" } ], "next_cursor": "…" }GET /api/certificates/issued/:id
One certificate with its event log (:id is the certificate id, for example OVC-7K3Q-M8PX-2DLA).
{
"certificate": {
"id": "OVC-7K3Q-M8PX-2DLA",
"type": { "id": "…", "title": "OSINT Fundamentals — Completion" },
"batch_id": "6f1c…",
"recipient": { "name": "Jane Doe", "email": "jane@example.com", "handle": "janedoe" },
"display_mode": "name",
"listed": false,
"indexable": false,
"fields": { "score": "92" },
"personal_message": "Great work on the final exercise.",
"issued_at": "2026-10-31T00:00:00.000Z",
"expires_at": null,
"status": "valid",
"public_status": "valid",
"revocation": null,
"superseded_by": null,
"supersedes": null,
"claimed_at": null,
"email": { "status": "sent", "sent_at": "2026-10-31T09:12:04.000Z", "error": null },
"view_count": 3,
"created_at": "2026-10-31T09:12:01.000Z",
"urls": { "html": "…", "json": "…", "pdf": "…", "png": "…" }
},
"events": [ { "kind": "emailed", "at": "…", "meta": { "status": "sent" } }, { "kind": "issued", "at": "…", "meta": { "source": "api" } } ]
}The recipient's email and the private fields (display_mode, listed, indexable, the private revocation reason) are visible to the issuer here and never on the public endpoint.
POST /api/certificates/issued/:id/revoke
{ "reason": "Issued to the wrong cohort", "public": true }Returns { "certificate": … } with status: "revoked". Only a valid certificate can be revoked; the action cannot be undone. Reissue after a typo from the console instead.
MCP: verify_credential
The OSINTverse MCP server has a read-only tool for agents:
{ "name": "verify_credential", "arguments": { "credential_id": "OVC-7K3Q-M8PX-2DLA" } }It returns the same JSON as the public verification endpoint: status, title, issuer and whether OSINTverse verified it, the recipient as disclosed, the signed record, the hash and signature, the keys URL, and the steps to check them. It needs the osint:read scope, never charges, and is available in sandbox and live mode. Agents are told that the issuer's and recipient's details are the issuer's statements, signed by OSINTverse at issue time, and that the signature does not vouch for the issuer unless the issuer is marked verified.
Issuing certificates
Set up an issuer, define credential types and layouts, issue from a form, a CSV, or the API, and handle claims, resends, reissues, and revocations.
Privacy and the recipient's choices
Display modes, unlisted pages, search-engine indexing, the public profile, what the issuer and the public see, and what account deletion does to a certificate.