# Integration API Specification
The service exposes an HTTP Restful API with the following endpoints.
# Internal API
The group of calls below is intended for the admin running the service itself.
Superadmin related
# Create an integrator account
Example
# Request
curl -X POST -H "Authorization: Bearer <superadmin-key>" https://server/v1/admin/accounts
# Request body
{
"cspUrlPrefix": "my-csp-url-prefix",
"cspPubKey": "hexBytes",
"name": "My integrator account",
"email": "admin@account.net"
}
# HTTP 200
{
"id": "1234567890",
"apiKey": "ksjdhfksjdh..." // The integrator (secret) key to use the API
}
# HTTP 400
{
"error": "Message goes here"
}
# Update an integrator account
Example
# Request
curl -X PUT -H "Authorization: Bearer <superadmin-key>" https://server/v1/admin/accounts/<id>
# Request body
{
"cspUrlPrefix": "my-csp-url-prefix",
"cspPubKey": "hexBytes",
"name": "My integrator account",
}
# HTTP 200
{
"id": "1234567890"
}
# HTTP 400
{
"error": "Message goes here"
}
# Reset an integrator API key
Example
# Request
curl -X PATCH -H "Authorization: Bearer <superadmin-key>" https://server/v1/admin/accounts/<id>/key
# HTTP 200
{
"id": "1234567890",
"apiKey": "priv_abcdefghijklmnoprstuvwxyz"
}
# HTTP 400
{
"error": "Message goes here"
}
# Get an integrator account
# Delete an integrator account
Example
# Request
curl -X DELETE -H "Authorization: Bearer <superadmin-key>" https://server/v1/admin/accounts/<id>
# HTTP 200
// empty response
# HTTP 400
{
"error": "Message goes here"
}
# Integrator API (Private)
Integrator related
The following endpoints are authenticated by using the integrator secret key. They allow integrators to manage the organizations related to their customers.
# Create an organization
Example
This request submits a transaction to the [voting blockchain](../architecture/services/vochain.md) which can take some time (~15 seconds) to be accepted and mined. Therefore, the return values of this method should not be considered valid until the Transaction Status method is called, using the `txHash` value to confirm that the desired transaction has been mined. Only then is it safe to query for the organization you have created.# Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/account/organizations
# Request body
{
"name": "Organization name",
"description": "my-description",
"header": "https://my/header.jpeg",
"avatar": "https://my/avatar.png"
}
# HTTP 200
{
"organizationId": "0x1234...",
"apiToken": "pub_qwertyui...", // API token for public voting endpoints
"txHash": "0x1234...",
}
# HTTP 400
{
"error": "Message goes here"
}
# Get an organization
Example
# Request
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/account/organizations/<organizationId>
# HTTP 200
{
"apiToken": "qoiuwhgoiauhsdaiouh", // the public API token
"name": "Organization name",
"description": "",
"header": "https://my/header.jpeg",
"avatar": "https://my/avatar.png"
}
# HTTP 400
{
"error": "Message goes here"
}
# Get an organization list
Retrieves the full list of organizations created by the given integrator key
Example
# Request
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/account/organizations
# HTTP 200
{
"organizations":
[
{
"api_token":"qoiuwhgoiauhsdaiouh",
"name":"Organization name",
"description":"",
"header": "https://my/header.jpeg",
"avatar": "https://my/avatar.png"
},
...
]
}
# HTTP 400
{
"error": "Message goes here"
}
# Remove an organization
Example
# Request
curl -X DELETE -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/account/organizations/<organizationId>
# HTTP 200
// empty response
# HTTP 400
{
"error": "Message goes here"
}
# Reset the public API token of an organization
Example
# Request
curl -X PATCH -H "Authorization: Bearer <integrator-key>" https://server/v1/account/organizations/<id>/key
# HTTP 200
{
"apiToken": "zxcvbnm"
}
# HTTP 400
{
"error": "Message goes here"
}
Organization related
These methods are also intended for integrators, but they are expected to do the duties of an organization managing a proposal.
# Set Organization metadata
Example
This request submits a transaction to the [voting blockchain](../architecture/services/vochain.md) which can take some time (~15 seconds) to be accepted and mined. Therefore, the return values of this method should not be considered valid until the Transaction Status method is called, using the `txHash` value to confirm that the desired transaction has been mined. Only then is it safe to query for the organization you have updated.# Request
curl -X PUT -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/metadata
# Request body
{
"name": "Organization name",
"description": "my-description",
"header": "https://my/header.jpeg",
"avatar": "https://my/avatar.png"
}
# HTTP 200
{
"organizationId": "0x1234...",
"contentUri": "ipfs://1234...",
"txHash": "0x1234...",
}
# HTTP 400
{
"error": "Message goes here"
}
# Check a transaction status
Example
# Request
curl -X GET -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/transactions/<transaction-hash>
# HTTP 200
{
"mined": true // true | false
}
# HTTP 400
{
"error": "Message goes here"
}
# Create an election
Generates a Merkle Tree with the given current census keys and generates a voting process with the given metadata.
Example
This request submits a transaction to the voting blockchain which can take some time (~15 seconds) to be accepted and mined. Therefore, the return values of this method should not be considered valid until the Transaction Status method is called, using thetxHash
value to confirm that the desired transaction has been mined. Only then is it safe to query for the election you have created. # Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/signed
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/blind
# Request body
{
"title": "Important election",
"description": "Description here",
"header": "https://my/header.jpeg",
"streamUri": "https://youtu.be/1234",
"startDate": "2021-10-25T11:20:53.769Z", // can be empty
"endDate": "2021-10-30T12:00:00.000Z",
"questions": [
{
"title": "Question 1",
"description": "(optional)",
"choices": ["Yes", "No", "Maybe"] // simplified version of title/value
}, {...}
],
"confidential": false, // Metadata access restricted to only census members
"hiddenResults": true, // Encrypt results until the election ends
"census": "<censusId>" // Optional for CSP processes
}
# HTTP 200
{
"electionId": "0x1234...",
"txHash": "0x1234...",
}
# HTTP 400
{
"error": "Message goes here"
}
# Set an election status
Generates a Merkle Tree with the given current census keys and generates a voting process with the given metadata.
Example
This request submits a transaction to the voting blockchain which can take some time (~15 seconds) to be accepted and mined. Therefore, the desired results of this method should not be considered valid until the Transaction Status method is called, using thetxHash
value to confirm that the desired transaction has been mined. Only then is it safe to query for the election you have updated. # Request
curl -X PUT -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/elections/<electionId>/status
# Request body
{
"status": "READY" // READY, ENDED, CANCELED, PAUSED
}
# HTTP 200
{
"txHash": "0x1234..."
}
# HTTP 400
{
"error": "Message goes here"
}
# List elections (filtered)
Allows unrestricted listing, paging and filtering for the integrator backend to display all info to organization admins.
Example
# Request
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/upcoming
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/active
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/paused
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/canceled
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/ended
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/blind
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/organizations/<organizationId>/elections/signed
# HTTP 200
[
{
"title": "Important election",
"description": "",
"header": "https://my/header.jpeg",
"status": "UPCOMING", // "UNKNOWN" | "PAUSED" | "CANCELED" | "UPCOMING" | "ACTIVE" | "ENDED"
"startDate": "2021-10-25T11:20:53.769Z", // can be empty
"endDate": "2021-10-30T12:00:00.000Z",
"proofType": "blind", // "blind | "ecdsa"
}, {...}
]
# HTTP 400
{
"error": "Message goes here"
}
# Get an election
Allows unrestricted access for the integrator backend to display all info to organization admins. Confidential elections do not require any additional step, just the integrator API key.
Example
# Request
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/elections/<electionId>
# Request body
{
"chainId": "vocdoni-main-1",
"type": "blind-confidential-hidden-results",
"organizationId": "20323909c3e0965d1489893db1512b32b55707ea",
"title": "test election",
"description": "description test 1",
"header": "https://source.unsplash.com/random/800x600",
"questions": [
{
"title": "test q1",
"description": "",
"choices": [
{
"title": "Yes",
"value": 0,
},
{
"title": "No",
"value": 1,
},
],
},
{
"title": "test q2",
"description": "",
"choices": [
{
"title": "Yes",
"value": 0,
},
{
"title": "No",
"value": 1,
},
],
},
{
"title": "test q3",
"description": "",
"choices": [
{
"title": "Yes",
"value": 0,
},
{
"title": "No",
"value": 1,
},
],
}
],
"status": "ENDED", // "UNKNOWN" | "PAUSED" | "CANCELED" | "UPCOMING" | "ACTIVE" | "ENDED"
"proofType": "blind", // "blind" | "ecdsa"
"streamUri": "uri",
"voteCount": 1,
"results": [
{
"title": [
"Yes",
"No"
],
"value": [
"1",
"0"
]
},
{
"title": [
"Yes",
"No"
],
"value": [
"1",
"0"
]
},
{
"title": [
"Yes",
"No"
],
"value": [
"1",
"0"
]
}
],
"organizationId": "20323909c3e0965d1489893db1512b32b55707ea",
"electionId": "47f2c1f1164a27db4f5e7b825f8ec064c44da88a83ff72b90e5755fff8bfb53b",
"aggregation": "discrete-counting",
"display": "multiple-question"
}
# HTTP 200
{
"electionId": "0x1234..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Create a census
A census where public keys or token slots (that will eventually contain a public key) are stored. A census can start with 0 items, and public keys can be imported later on.
If census tokens are allocated, users will need to generate a wallet on the frontend and register the public key by themselves. This prevents both the API and the integrator from gaining access to the private key.
Example
# Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses
# Request body
{
"name": "2021 members"
}
# HTTP 200
{
"censusId": "123456789..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Add N census tokens (once)
Creates N census tokens for voters to register their public key in the future.
Example
# Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/tokens/flat
# Request body
{
"size": 450
}
# HTTP 200
{
"censusId": "123456789...",
"size": 700, // new size
"tokens": [
"jashdlkfjahs", "uyroeituwyert", "e7rg9e87rn9", ... // x 450
]
}
# HTTP 400
{
"error": "Message goes here"
}
# Add weighted census tokens (once)
Creates weighted census tokens so that voters with the token can register their public key to the appropriate census weight.
Example
# Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/tokens/weighted
# Request body
{
"weights": [
40, 70, 200
]
}
# HTTP 200
{
"censusId": "123456789...",
"size": 700, // new size
"tokens": [
{ "token": "jashdlkfjahs", "weight": 40 },
{ "token": "uyroeituwyert", "weight": 70 },
{ "token": "e7rg9e87rn9", "weight": 200 }
]
}
# HTTP 400
{
"error": "Message goes here"
}
# Get census token (see a registered public key)
Allows integrators to check the status of a census token, given to a user.
If the token has already been redeemed, the public key will be used as part of the census normally.
Example
# Request
curl -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/tokens/<tokenId>
# HTTP 200
{
"publicKey": "", // no public key yet
"weight": 20
}
# HTTP 400
{
"error": "Message goes here"
}
# Remove a census token or public key from a census
Removes the given token or key from the given census. The next time it is used, the new key will be in effect.
Example
# Request
curl -X DELETE -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/tokens/<tokenId>
curl -X DELETE -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/keys/<publicKey>
# HTTP 200
// empty response
# HTTP 400
{
"error": "Message goes here"
}
# Import public keys into a census
Import a group of public keys to an existing census. All voters have the same weight (1).
Example
# Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/import/flat
# Request body
{
"publicKeys": [
"0x0312345678...",
"0x0223456789...",
...
]
}
# HTTP 200
{
"censusId": "123456789...",
"size": 300
}
# HTTP 400
{
"error": "Message goes here"
}
# Import weighted public keys into a census
Import a group of public keys to an existing census, using their respective weight.
Example
# Request
curl -X POST -H "Authorization: Bearer <integrator-key>" https://server/v1/priv/censuses/<censusId>/import/weighted
# Request body
{
"entries": [
{ "publicKey": "0x0312345678...", "weight": 100 },
{ "publicKey": "0x02234567890...", "weight": 150 },
...
]
}
# HTTP 200
{
"censusId": "123456789...",
"size": 300
}
# HTTP 400
{
"error": "Message goes here"
}
# Public API
(token API authenticated, voter apps call it directly)
# Get election list (per organization) – non-confidential
Example
# Request
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/upcoming
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/active
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/paused
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/canceled
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/ended
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/blind
curl -H "Authorization: Bearer <manager-key>" https://server/v1/pub/organizations/<organizationId>/elections/signed
# HTTP 200
[
{
"id": "0x12345678...",
"title": "Important election",
"avatar": "https://my/avatar.png",
"startDate": "2021-12-25T11:20:53.769Z",
"endDate": "2021-10-30T12:00:00.000Z",
"confidential": false,
"hiddenResults": true,
"proofType": "blind", // "blind | "ecdsa"
}, {...}
]
# HTTP 400
{
"error": "Message goes here"
}
# Get election info – non-confidential
Example
# Request
curl -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/elections/<electionId>
# HTTP 200
{
"chainId": "vocdoni-main-1",
"type": "signed-plain", // blind-plain, ...
"title": "Important election",
"description": "Description goes here",
"header": "https://my/header.jpeg",
"streamUri": "https://youtu.be/1234",
"questions": [
{
"title": "Question 1",
"description": "(optional)",
"choices": [
{
"title": "Yes",
"value": 0,
},
{
"title": "No",
"value": 1,
},
{
"title": "Maybe",
"value": 2,
},
],
}, {...}
],
"status": "UPCOMING", // "UNKNOWN" | "PAUSED" | "CANCELED" | "UPCOMING" | "ACTIVE" | "ENDED"
"voteCount": 1234,
"proofType": "blind", // "blind" | "ecdsa"
"results": [ // Empty array when no results []
[ { "title": "Yes", "value": "1234" }, { "title": "No", "value": "2345" } ],
[ { "title": "Yes", "value": "22" }, { "title": "No", "value": "33" } ]
]
}
# HTTP 400
{
"error": "Message goes here"
}
# Get election info – confidential
Provides the details of a confidential voting process if the user holds a wallet that belongs to its census.
If {electionId}
has been signed by the CSP, then it gets the Vochain parameters, decrypts the metadata and returns it to the caller.
URL Params:
- election-id
- signed-pid:
sign({electionId}, voterPrivK)
- csp-signature:
sign({electionId}, cspPrivK)
Example
# Request
curl -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/elections/<election-id>/auth/<csp-shared-key>
# HTTP 200
{
"chainId": "vocdoni-main-1",
"type": "signed-plain", // blind-plain, ...
"title": "Important election",
"description": "Description goes here",
"header": "https://my/header.jpeg",
"streamUri": "https://youtu.be/1234",
"questions": [
{
"title": "Question 1",
"description": "(optional)",
"choices": [
{
"title": "Yes",
"value": 0,
},
{
"title": "No",
"value": 1,
},
{
"title": "Maybe",
"value": 2,
},
],
}, {...}
],
"status": "UPCOMING", // "UNKNOWN" | "PAUSED" | "CANCELED" | "UPCOMING" | "ACTIVE" | "ENDED"
"proofType": "blind", // "blind" | "ecdsa"
"voteCount": 1234,
"results": [ // Empty array when no results []
[ { "title": "Yes", "value": "1234" }, { "title": "No", "value": "2345" } ],
[ { "title": "Yes", "value": "22" }, { "title": "No", "value": "33" } ]
]
}
# HTTP 400
{
"error": "Message goes here"
}
# Requesting a census proof
People voting on a signed process will need to package a vote envelope using the result of this call.
Note: This call does not apply to deployments where a custom CSP validation is being used.
Example
# Request
curl -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/elections/<electionId>/proof
# Request body
{
"signature": "0x12345678..." // signing a well-known payload using the wallet
}
# HTTP 200
{
"proof": "..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Submitting a vote (signed or blind)
Voters using the tiny JS SDK will get a base64 bundle including the vote and the census proof. This payload is submitted as a base64 string.
Example
# Request
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/elections/<electionId>/vote
# Request body
{
"vote": "<base64-signed-vote-transaction>" // see dvote-js
}
# HTTP 200
{
"nullifier": "0x12345678..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Getting a ballot (nullifier)
Voters can check the status of their vote here, and eventually check the explorer link, for independent confirmation.
Example
# Request
curl -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/nullifiers/<nullifier>
# HTTP 200
{
"electionId": "0x12345678...",
"registered": true,
"explorerUrl": "https://vaas.explorer.vote/nullifiers/0x12345678"
}
# HTTP 400
{
"error": "Message goes here"
}
# Registering a voter's public key
This process needs to be done by the integrator's frontend, once.
As soon as a user runs an updated UI version, a new private key should be generated, and the public key should be registered, so that new votes can use this key.
If the wallet is lost, the integrator will need to remove the pubKey from the census and create a new census token when the new wallet is available.
Example
# Request
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/censuses/<censusId>/token
# Request body
{
"censusToken": "jashdlkfjahs",
"publicKey": "0x03abcdef0123456789..."
}
# HTTP 200
// empty response
# HTTP 400
{
"error": "Message goes here"
}
# Authentication API
Generic authentication
# Get a shared key to access the private data of an election
The CSP issues a per-process signature whenever the wallet belongs to the process's census. The signature can be used to retrieve confidential information, restricted to only census members.
The voter signs the electionID
to prove that he/she has a private key within the election census. If everything is correct, the CSP returns sign({electionId}, cspPrivK)
.
- election-id
- signed-pid:
sign({electionId}, voterPrivK)
Example
# Request
curl -H "Authorization: Bearer <organization-api-token>" https://server/v1/auth/elections/<election-id>/sharedkey
# Request body
{
"authData": ["<signed-pid>"]
}
# HTTP 200
{
"sharedkey": "0x1234567890abcde..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Get a token for requesting a plain/blind signature
The blind signature process involves a two step interaction.
In the first interaction, the voter proves to have a private key within the election census. If everything is correct, the backend replies with the tokenR
, which the voter needs to use on the second step.
- process-id
- signed-pid:
sign({electionId}, voterPrivK)
Example
# Request
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/auth/elections/<election-id>/ecdsa/auth
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/auth/elections/<election-id>/blind/auth
# Request body
{
"authData": ["<signed-pid>"]
}
# HTTP 200
{
"tokenR": "0x1234567890abcde..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Request the plain/blind signature for an ephemeral wallet
The user generates an ephemeral wallet and the received tokenR to generate a (plain or blinded) payload. This payload is sent to the backend, which will check the correctness and reply with a signature of the payload.
The voter then may unblind the response (if applicable) and use it as their vote signature.
Example
# Request
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/auth/elections/<electionId>/ecdsa/sign
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/auth/elections/<electionId>/blind/sign
# Request body
{
"payload": "0xabcdef...", // hash({electionId, address}) or blind(hash({electionId, address}))
"tokenR": "0x1234567890abcde..."
}
# HTTP 200
{
"signature": "0x1234567890abcde..." // plain or blind signature
}
# HTTP 400
{
"error": "Message goes here"
}
# Get the public keys that have requested a blind signature on an election
For transparency, external observers can request the exhaustive list of public keys that made a blind signature request.
Example
# Request
curl -H "Authorization: Bearer <organization-api-token>" https://server/v1/pub/elections/<electionId>/blind/authorized
# HTTP 200
{
"publicKeys": [
"0x12345678...",
"0x23456789...",
...
]
}
# HTTP 400
{
"error": "Message goes here"
}
Custom authentication API
# Get a shared key to access the private data of an election
This endpoint is conceptually the same as the one from above. The only difference lies on the custom logic that decides whether a voter is eligible or not.
The CSP issues a per-process signature whenever the wallet belongs to the process's census. The signature can be used to retrieve confidential information, restricted to only census members.
If the evidence provided is correct, the CSP returns sign({electionId}, cspPrivK)
.
- election-id
- signed-pid:
sign({electionId}, voterPrivK)
Example
# Request
curl -X POST -H "Authorization: Bearer <organization-api-token>" https://server/v1/auth/elections/<election-id>/sharedkey
# Request body
{
"authData": ["param1", "param2", ...]
}
#### HTTP 200
```json
{
"sharedkey": "0x1234567890abcde..."
}
# HTTP 400
{
"error": "Message goes here"
}
# Get a token for requesting a blind signature - custom
This endpoint is conceptually the same as the one from above. The only difference lies on the custom logic that decides whether a tokenR
is generated or not.
The blind signature process involves a two step interaction.
In the first interaction, the voter proves their eligibility. If everything is correct, the backend replies with the tokenR
, which the voter needs to use on the second step.