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.
| Parameter | Required | Type | Notes |
|---|---|---|---|
response_type | yes | string | Always code. |
client_id | yes | string | Your registered client identifier, see Registering your client. |
redirect_uri | yes | string | A registered redirect URL. Must match the one you send to the token endpoint. |
scope | yes | string | service or credential. |
state | yes | string | Up to 255 bytes, passed back unchanged. Your handle on the flow; keep it server-side and single use. |
lang | no | string | Language preference, for example en-US. |
credentialID | conditional | string | Required when scope=credential. |
numSignatures | conditional | integer | Required when scope=credential. Maximum 50. |
hash | conditional | string | Required when scope=credential. Comma-separated, base64url encoded hashes; the number of entries must equal numSignatures. |
account_token | no | string | See 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.
| Parameter | Required | Type | Notes |
|---|---|---|---|
grant_type | yes | string | Always authorization_code. |
code | yes | string | The code from the redirect. |
client_id | yes | string | Your client identifier. |
redirect_uri | yes | string | The 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 field | Required | Notes |
|---|---|---|
access_token | yes | With scope=service a Bearer token for the /csc/v1/* calls; with scope=credential the signature activation data (SAD). |
token_type | yes | Bearer or SAD. |
expires_in | no | Seconds. 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
Which credentials the signer holds. Needs the service Bearer token.
| Parameter | Required | Type | Notes |
|---|---|---|---|
maxResults | no | integer | Maximum number of credentialIDs returned. |
pageToken | no | string | Opaque token for the next page. |
clientData | no | string | Passed through, see CSC 7.5. |
| Response field | Required | Notes |
|---|---|---|
credentialIDs | yes | Array 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
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.
| Parameter | Required | Type | Notes |
|---|---|---|---|
credentialID | yes | string | From credentials/list. |
| Response field | Required | Notes |
|---|---|---|
key/status | yes | enabled (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/algo | yes | Array of OIDs of supported key algorithms. |
key/len | yes | Key length in bits. Divide by 8 for the signature length you must reserve. |
cert/certificates | yes | One or more base64-encoded X.509v3 certificates, signer first. |
authMode | yes | Always 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
Signs your hash or hashes. Needs both tokens: the service Bearer token in the header and the SAD in the body.
| Parameter | Required | Type | Notes |
|---|---|---|---|
credentialID | yes | string | The credential the SAD was issued for. |
SAD | yes | string | The access_token from the credential scope exchange. |
hash | yes | array of string | The hashes, standard base64 (not base64url as in the authorize URL). SHA-256. |
hashAlgo | yes | string | Must be 2.16.840.1.101.3.4.2.1 (SHA-256). |
signAlgo | yes | string | Must be 1.2.840.113549.1.1.1 (rsaEncryption). See the note below. |
clientData | no | string | Passed through, see CSC 7.5. |
| Response field | Required | Notes |
|---|---|---|
signatures | yes | Array 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 request | 1.2.840.113549.1.1.1 rsaEncryption |
hashAlgo in the signHash request | 2.16.840.1.101.3.4.2.1 SHA-256 |
| In the CMS your library builds | 1.2.840.113549.1.1.11 sha256WithRSAEncryption |
DSS SignatureAlgorithm | RSA_SHA256 |
| pyHanko | an 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 lifetime | 3600 seconds |
| SAD lifetime | 300 seconds |
| Hashes per authorization | 50 maximum, numSignatures must match the list |
state | up to 255 bytes |
| Hash encoding, authorize URL | base64url, comma-separated |
| Hash encoding, signHash body | standard 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.