Skip to content
CredenShare
Secure requests

Read submissions

GET /v1/requests/{shortCode}/submissions — the ciphertext somebody submitted, which only you can open.
GET /v1/requests/{shortCode}/submissions

Requires the requests:read scope. This is the only read on the /v1 surface that returns content.

Why this endpoint is allowed to return content

Everywhere else, an API read returns metadata, because a bearer key skips the proof-of-work and captcha challenges that guard the anonymous recipient page — exposing recipient reads to a key would turn the API into an enumeration bypass.

This endpoint is not an exception to that rule; it is the rule working. Each submission is sealed to the request's public key. The private half never reached us: it lives as a seed in the owner's access-link fragment, or wrapped under their account key. We hand back something we cannot open, to the party who can.

That is what makes an ephemeral automation possible. A runner derives its keypair, is granted the account key once, and can then unwrap every submission it is handed without keeping any local state.

Request

curl https://api.credenshare.io/v1/requests/a1b2c3d4/submissions \
  -H "Authorization: Bearer crs_sk_live_<keyId>.<authSecret>"

Success response

200 OK:

{
  "submissions": [
    {
      "short_code": "e5f6g7h8",
      "created_at": "2026-09-03T14:22:11Z",
      "data": "<base64 ciphertext>",
      "encryption_type": "e2ee-aes256-gcm"
    }
  ],
  "count": 1
}

count is the number of entries in submissions, which is not necessarily the number of submissions the request received — see the next section.

data is ciphertext. Decrypt it with the private key matching the public_key you supplied on create. submissions is always an array; a request nobody has filled in yet returns [] and count: 0.

Each submission has its own short_code, distinct from the request's.

Legacy submissions are skipped, and counted

Submissions that are not client-encrypted are omitted from the response rather than returned.

A request that predates end-to-end encryption may hold submissions stored under the older server-side encryption types. For those rows the server can decrypt, so returning them would make this the one place in the product where a bearer-authenticated call yields readable secrets. They are skipped.

The omission is named in the response rather than merely logged:

{
  "submissions": [],
  "count": 0,
  "skipped_not_end_to_end_encrypted": 2
}

The key is present only when the count is greater than zero. Without it you would see fewer submissions than your dashboard shows and have no way to learn why. If you see it, retrieve those submissions through the app instead.

Check encryption_type on each entry you do receive; every returned row is client-encrypted, but reading the field rather than assuming keeps your client correct if another client-side type is added later.

Ownership

Ownership is checked twice: once in the handler before anything else runs, and again inside the export path against the viewing user. Submissions are the most sensitive thing this API can return, so the check is not delegated to a service that might later relax it for the recipient path.

A short code on another account returns 404, the same as a short code that does not exist.

Errors

Statuserror_codeWhen
40044No short code in the path. GET /v1/requests/submissions is not a bulk read.
40378The key lacks requests:read.
40470No such request on this account.
429107Rate limit exceeded. Retry-After gives the seconds to wait.
50011The submissions could not be read.

Composition

request.submitted fires
  → GET /v1/requests/{shortCode}/submissions
  → decrypt with your private key
  → use the credential

Subscribing to request.submitted is cheaper and faster than polling this endpoint. See Webhook events.