Reference

API reference

Every endpoint of the Signing API, with the parameters Cleverbase actually accepts and the values it requires. The flow that ties these together is the chapter "The signing flow"; this one is for looking things up while you write code.

Base URL: https://connect.acc.cleverbase.com on acceptance, https://connect.cleverbase.com in production. The API follows CSC API v1.0.4 with OAuth 2.0 (RFC 6749); where the CSC specification allows more than Cleverbase supports, this reference says what Cleverbase supports.

GET /oauth2/authorize

CSC API v1.0.4, §8.3 · RFC 6749, §3.1

Starts an authorization. Two scopes, two purposes: service gets you a Bearer token for the credential lookup, credential gets you the signature activation data (SAD) for one set of hashes. This is a browser redirect, not a call from your backend.

ParameterRequiredTypeNotes
response_typeyesstringAlways code.
client_idyesstringYour registered client identifier, see Registering your client.
redirect_uriyesstringA registered redirect URL. Must match the one you send to the token endpoint.
scopeyesstringservice or credential.
stateyesstringUp to 255 bytes, passed back unchanged. Your handle on the flow; keep it server-side and single use.
langnostringLanguage preference, for example en-US.
credentialIDconditionalstringRequired when scope=credential.
numSignaturesconditionalintegerRequired when scope=credential. Maximum 50.
hashconditionalstringRequired when scope=credential. Comma-separated, base64url encoded hashes; the number of entries must equal numSignatures.
account_tokennostringSee Account tokens.

The response is a redirect to your redirect_uri with code and state, or with error and error_description and the same state.

GET /oauth2/authorize?response_type=code&client_id=<client_id>
    &redirect_uri=<redirect_uri>&scope=service&lang=en-US&state=12345678:root

HTTP/1.1 302 Found
Location: <redirect_uri>?code=FhkXf9P269L8g&state=12345678:root
GET /oauth2/authorize?response_type=code&client_id=<client_id>
    &redirect_uri=<redirect_uri>&scope=credential
    &credentialID=GX0112348&numSignatures=1
    &hash=MTIzNDU2Nzg5MHF3ZXJ0enVpb3Bhc2RmZ2hqa2zDtnl4
    &state=12345678:root

HTTP/1.1 302 Found
Location: <redirect_uri>?code=HS9naJKWwp901hBcK348IUHiuH8374&state=12345678:root
HTTP/1.1 302 Found
Location: <redirect_uri>?error=invalid_request
    &error_description=Invalid%20Authorization%20Code&state=12345678:root

POST /oauth2/token

CSC API v1.0.4, §8.4 · RFC 6749, §3.2

Exchanges an authorization code for a token. Same endpoint for both scopes; what comes back differs.

ParameterRequiredTypeNotes
grant_typeyesstringAlways authorization_code.
codeyesstringThe code from the redirect.
client_idyesstringYour client identifier.
redirect_uriyesstringThe same redirect URL you used in the authorize request.

Send your credentials as HTTP basic auth on client_id:client_secret (RFC 6749, §2.3.1), and the parameters as application/x-www-form-urlencoded.

Response fieldRequiredNotes
access_tokenyesWith scope=service a Bearer token for the /csc/v1/* calls; with scope=credential the signature activation data (SAD).
token_typeyesBearer or SAD.
expires_innoSeconds. In practice 3600 for a service token and 300 for a SAD.
POST /oauth2/token HTTP/1.1
Authorization: Basic Y2xpZW50SUQ6cGFzc3dvcmQ
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=FhkXf9P269L8g
  &client_id=<client_id>&redirect_uri=<redirect_uri>
HTTP/1.1 200 OK
{ "access_token": "4/CKN69L8gdSYp5_pwH3XlFQZ3ndFhkXf9P2_TiHRG-bA",
  "token_type": "Bearer", "expires_in": 3600 }
HTTP/1.1 200 OK
{ "access_token": "3XlFQZ3ndFhkXf9P24/CKN69L8gdSYp5H3XlFQZ3ndFhkXf9P2",
  "token_type": "SAD", "expires_in": 300 }

POST /csc/v1/credentials/list

CSC API v1.0.4, §11.4

Which credentials the signer holds. Needs the service Bearer token.

ParameterRequiredTypeNotes
maxResultsnointegerMaximum number of credentialIDs returned.
pageTokennostringOpaque token for the next page.
clientDatanostringPassed through, see CSC 7.5.
Response fieldRequiredNotes
credentialIDsyesArray of credential identifiers.
POST /csc/v1/credentials/list HTTP/1.1
Authorization: Bearer 4/CKN69L8gdSYp5_pwH3XlFQZ3ndFhkXf9P2_TiHRG-bA
Content-Type: application/json

{ "maxResults": 10 }
HTTP/1.1 200 OK
{ "credentialIDs": [ "GX0112348", "HX0224685" ] }

POST /csc/v1/credentials/info

CSC API v1.0.4, §11.5

The certificate chain and the key's properties. Needs the service Bearer token. This is the call that lets you build the placeholder and compute the hash, so it comes before any PDF work.

ParameterRequiredTypeNotes
credentialIDyesstringFrom credentials/list.
Response fieldRequiredNotes
key/statusyesenabled (usable for signing) or disabled. Disabled happens when the owner disabled the key, or when we detected that the certificate expired or was revoked. Check this before you continue.
key/algoyesArray of OIDs of supported key algorithms.
key/lenyesKey length in bits. Divide by 8 for the signature length you must reserve.
cert/certificatesyesOne or more base64-encoded X.509v3 certificates, signer first.
authModeyesAlways oauth2code.
POST /csc/v1/credentials/info HTTP/1.1
Authorization: Bearer 4/CKN69L8gdSYp5_pwH3XlFQZ3ndFhkXf9P2_TiHRG-bA
Content-Type: application/json

{ "credentialID": "GX0112348" }
HTTP/1.1 200 OK
{ "key":  { "status": "enabled", "algo": [ "1.2.840.113549.1.1.1" ], "len": 2048 },
  "cert": { "certificates": [ "MIIGDSGDGSD...." ] },
  "authMode": "oauth2code" }

POST /csc/v1/signatures/signHash

CSC API v1.0.4, §11.9

Signs your hash or hashes. Needs both tokens: the service Bearer token in the header and the SAD in the body.

ParameterRequiredTypeNotes
credentialIDyesstringThe credential the SAD was issued for.
SADyesstringThe access_token from the credential scope exchange.
hashyesarray of stringThe hashes, standard base64 (not base64url as in the authorize URL). SHA-256.
hashAlgoyesstringMust be 2.16.840.1.101.3.4.2.1 (SHA-256).
signAlgoyesstringMust be 1.2.840.113549.1.1.1 (rsaEncryption). See the note below.
clientDatanostringPassed through, see CSC 7.5.
Response fieldRequiredNotes
signaturesyesArray of signature values, in the same order as the hashes you sent.
POST /csc/v1/signatures/signHash HTTP/1.1
Authorization: Bearer 4/CKN69L8gdSYp5_pwH3XlFQZ3ndFhkXf9P2_TiHRG-bA
Content-Type: application/json

{ "credentialID": "GX0112348",
  "SAD": "3XlFQZ3ndFhkXf9P24/CKN69L8gdSYp5H3XlFQZ3ndFhkXf9P2",
  "hash": [ "sTOgwOm+474gFj0q0x1iSNspKqbcse4IeiqlDg/HWuI=" ],
  "hashAlgo": "2.16.840.1.101.3.4.2.1",
  "signAlgo": "1.2.840.113549.1.1.1" }
HTTP/1.1 200 OK
{ "signatures": [ "KedJuTob5gtvYx9qM3k3gm7kbLBwVbEQRl26S2tmXjqNND7MRGtoew==" ] }

The signAlgo value, and what your library must name

signAlgo is 1.2.840.113549.1.1.1, which is rsaEncryption: the key algorithm, not a combined hash-and-signature algorithm. Together with hashAlgo SHA-256 it produces a PKCS#1 v1.5 signature over a SHA-256 digest.

In your PDF library that same signature is called sha256WithRSAEncryption (1.2.840.113549.1.1.11), and that is the OID that must end up in the CMS signatureAlgorithm. So the two sides use different names for one thing:

Value
signAlgo in the signHash request1.2.840.113549.1.1.1 rsaEncryption
hashAlgo in the signHash request2.16.840.1.101.3.4.2.1 SHA-256
In the CMS your library builds1.2.840.113549.1.1.11 sha256WithRSAEncryption
DSS SignatureAlgorithmRSA_SHA256
pyHankoan RSA certificate with digest sha256

Do not "fix" the mismatch by putting rsaEncryption in the CMS: a validator expects the combined OID there.

Numbers worth remembering

Service token lifetime3600 seconds
SAD lifetime300 seconds
Hashes per authorization50 maximum, numSignatures must match the list
stateup to 255 bytes
Hash encoding, authorize URLbase64url, comma-separated
Hash encoding, signHash bodystandard base64, JSON array

Errors

Authorization failures come back on the redirect as error plus error_description, with your state, and never as an HTTP error to your backend: the browser is the one talking to us. A refusal in the app is such a redirect too, so treat "error" and "the signer said no" as one path. The /csc/v1/* calls answer your backend directly, so those failures arrive as HTTP status codes with a body.

There is no webhook. If the signer never returns, you are not told; see "The signer's journey" for how to time that out.