Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ An interactive version of the documentation is hosted on https://developer.kivra
## Deployment

This project is deployed automatically when a commit is pushed to `master`.
Feature branches are also deployed to `https://developer.kivra.com/branch-name`.

## Development

Preview changes to `swagger.yml` using docker-compose:

```bash
$ docker-compose up
```
10 changes: 9 additions & 1 deletion cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
steps:
- name: 'gcr.io/cloud-builders/gsutil'
args: ['cp', 'index.html', 'swagger.yml', 'gs://developer.kivra.com']
entrypoint: 'bash'
args:
- '-c'
- '[[ "$BRANCH_NAME" != "master" ]] && gsutil cp index.html swagger.yml "gs://developer.kivra.com/$BRANCH_NAME/" || echo "skipping . . ."'
- name: 'gcr.io/cloud-builders/gsutil'
entrypoint: 'bash'
args:
- '-c'
- '[[ "$BRANCH_NAME" == "master" ]] && gsutil cp index.html swagger.yml "gs://developer.kivra.com/" || echo "skipping . . ."'
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: "3.8"
services:
web:
image: nginx:stable-alpine
volumes:
- .:/usr/share/nginx/html
ports:
- "1337:80"
311 changes: 311 additions & 0 deletions swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,167 @@ paths:
items:
$ref: '#/components/schemas/CompanyList'

# ##############################################
# POST /v2/tenant/TKEY/content
# ##############################################
/v2/tenant/{tenantKey}/content:
post:
tags:
- "Tenant API - Content"
summary: Send content v2 to a recipient (user or company)
operationId: Send content v2
description: |
Metadata is data that Kivra needs to send the Content to the right User. It may also determine how a User can interact with the Content.

### Minimum Metadata
As a minimum a valid `ssn` or `vat_number` is required.

<aside class="notice">
Note: Kivra will reject Content using both `ssn` and `vat_number` in the same metadata as this is ambiguous.
</aside>

### Retained Content
A retained Content is a Content that is sent to a Recipient who is not yet a user of Kivra. Once that Recipient register with Kivra, the Content will be delivered to that Recipient’s Kivra account. Retained Content has a time limit for how long they can be retained before being deleted, ie removed unless the Recipient registers within a given period of time.

<aside class="notice">
Note: Usage of retained content is only allowed in certain specific cases and its usage needs to be regulated in the business relationship between the sender party and Kivra.
</aside>

Sending retained Content uses all the same attributes as a normal Content with the difference for some additional metadata-attributes.
By enabling the `retain` metadata attribute and setting it to `true` will enable possible retention of a Content. Kivra’s logic is to first look if the Recipient exist. If it does Kivra will deliver the Content as usual, if the Recipient doesn’t exist Kivra will retain the Content for the default amount of time (*30 days*). This makes it easy for an Integrator to issue a "retain or deliver"-logic for all it’s Contents.

If an integrator want to retain a Content for a another time-period than Kivra’s default if can be done via the `retention_time` additional metadata.

### Duplicate control
If exactly the same payload (content plus metadata, with only exception being the `generated_at` field) is received more than once, only the first occurrence will result in a delivery. In the other occurrences an OK message will be returned, but no corresponding content will be delivered to the receiver. This is a security mechanism to allow senders to safely re-send the same payload in case there is uncertainty on whether the previous sending resulted in a delivery or not. A typical example is in case of timeout error, where the sender cannot establish whether the sending resulted in a content delivery or not.

<aside class="notice">
Note: Duplicate control is only available in production, not in sandbox. This allows senders to reuse the same payload several times in the test environment.
</aside>

It's important to underline that the duplicate control is made by checking the complete payload (beside `generated_at`), not only the attached content. This means that any change in the payload will cause the duplicate check to fail and the corresponding content to be delivered.

Another important consequence is that in case a sender wants to safely re-send a PDF content, it is important that the PDF is not generated again between the API calls as this almost certainly results in a slightly different PDF and therefore in a different payload, meaning that the duplication check will not be able to recognise it as a duplicate.

### Responsive Content Lifecycle
When sending html content it's required to provide a PDF version of the document as the base `Part`. Inside this base `Part` you can then assign the responsive html version of the document as a `ResponsivePart` under the key: `responsive_part`.
security:
- oAuth2Client:
- 'post:kivra.v2.tenant.{tenantKey}.content'
parameters:
- name: tenantKey
in: path
description: The unique Key for a Tenant
required: true
schema:
type: string
format: hexadecimal
responses:
201:
description: |
Content Created succesfully
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Content_user_v2'
- $ref: '#/components/schemas/Content_company_v2'
headers:
kivra-objkey:
description: Object Key
schema:
type: string
format: 'hexadecimal value'
kivra-retained:
description: |
Boolean denoting if a Content was Retained, Note: this header is **only** returned when a Content is retained
schema:
type: boolean
location:
description: URL to created Object
schema:
type: string
format: url
requestBody:
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Content_user_v2'
- $ref: '#/components/schemas/Content_company_v2'
examples:
responsive content to user:
value:
ssn: '191212121212'
subject: Sample Responsive Content to User
generated_at: '2020-05-25'
retain: false
tenant_info: string
parts:
- name: filename.pdf
content_type: application/pdf
data: JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1BhZ2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAgL0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVudCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAvU3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21hbgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29udGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBUagogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAwMDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAgICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==
responsive_part:
name: filename.html
content_type: text/html
data: PCFkb2N0eXBlIGh0bWw+CjxoZWFkPgogIDxtZXRhIGNoYXJzZXQ94oCddXRmLTjigJ0+CiAgPHRpdGxlPlRlc3Q8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5PgogIDxwPlRoaXMgaXMgb25seSBhIHRlc3QuPC9wPgo8L2JvZHk+
invoice to user:
value:
ssn: '191212121212'
subject: Sample Invoice to User
generated_at: '2020-05-25'
retain: false
tenant_info: string
parts:
- name: filename.pdf
content_type: application/pdf
data: REVBREJFRUY=
payment:
payable: true
status: unpaid
currency: SEK
due_date: '2021-01-01'
total_owed: '123.50'
type: SE_OCR
method: '1'
account: '12345'
reference: 123OCRNUMBER456
variable_amount: false
min_amount: '50.00'
content to user:
value:
ssn: '191212121212'
subject: Sample Content to User
generated_at: '2020-05-25'
retain: false
tenant_info: string
parts:
- name: filename.pdf
content_type: application/pdf
data: REVBREJFRUY=
payslip to user:
value:
ssn: '191212121212'
subject: Sample Payslip to User
generated_at: '2020-05-25'
retain: true
retention_time: '390'
tenant_info: string
parts:
- name: filename.pdf
content_type: application/pdf
data: REVBREJFRUY=
content to company:
value:
vat_number: SE556840226601
subject: Sample Content to Company
generated_at: '2020-05-25'
tenant_info: string
parts:
- name: filename.pdf
content_type: application/pdf
data: REVBREJFRUY=

# ##############################################
# POST /v1/tenant/TKEY/content
# ##############################################
Expand Down Expand Up @@ -1704,6 +1865,101 @@ paths:
components:
schemas:

# ##############################################
# SCHEMA Content_user_v2
# ##############################################
Content_user_v2:
type: object
required:
- ssn
properties:
ssn:
description: |
User's unique SSN, format: `YYYYMMDDnnnn`
type: string
writeOnly: true
example: "191212121212"
subject:
description: This Subject/Title will be visibile in the Recipients Inbox.
type: string
example: "Sample Invoice"
generated_at:
type: string
format: ISO8601
example: "2016-12-12"
description: |
Optional attribute which denotes when a specific Content was generated at the tenant/integrator’s site. The attribute will be used for sorting in the Kivra user interface, which makes it possible for a tenant or integrator to control the sorting.
retain:
description: |
Boolean denoting if Kivra should try and retain this Content if it can’t be delivered. Default `false`. Please note that retain must never be set to `true` for payable content.
type: boolean
writeOnly: true
readOnly: true
example: false
retention_time:
description: |
How long to retain a Content. Supported values: `"30"` and `"390"`
type: string
writeOnly: true
readOnly: true
example: "30"
enum: ["30","390"]
tenant_info:
description: |
An arbitrary string defined by the tenant, used to group content for administrative tasks
type: string
writeOnly: true
readOnly: true
parts:
description: Array of file Objects
type: array
writeOnly: true
items:
oneOf:
- $ref: '#/components/schemas/Part'
payment:
$ref: '#/components/schemas/Payment'

# ##############################################
# SCHEMA Content_company_v2
# ##############################################
Content_company_v2:
type: object
required:
- vat_number
properties:
vat_number:
description: |
A valid VAT-identifier, Swedish format: `SE[xxxxxxxxxx]01`
type: string
readOnly: true
writeOnly: true
example: SE556840226601
subject:
description: This Subject/Title will be visibile in the Recipients Inbox.
type: string
example: "Sample Invoice"
generated_at:
type: string
format: ISO8601
example: "2016-12-12"
description: |
Optional attribute which denotes when a specific Content was generated at the tenant/integrator’s site. The attribute will be used for sorting in the Kivra user interface, which makes it possible for a tenant or integrator to control the sorting.
tenant_info:
description: |
An arbitrary string defined by the tenant, used to group content for administrative tasks
type: string
writeOnly: true
readOnly: true
parts:
description: Array of file Objects
type: array
writeOnly: true
items:
$ref: '#/components/schemas/Part'
payment:
$ref: '#/components/schemas/Payment'

# ##############################################
# SCHEMA Content_user
# ##############################################
Expand Down Expand Up @@ -2255,6 +2511,61 @@ components:
type: string
example: "SE556840226601"

# ##############################################
# SCHEMA Responsive Part
# ##############################################
ResponsivePart:
type: object
required:
- name
- data
- content_type
properties:
name:
description: Arbritrary file-name that is shown alongside the File in the Kivra GUI
type: string
example: filename.html
data:
description: Base64-encoded data
type: string
format: 'Base64-encoded data'
example: REVBREJFRUY=
content_type:
description: The [Iana](http://www.iana.org/assignments/media-types/media-types.xhtml) Media type corresponding to the file, e.g. "text/html"
type: string
example: "text/html"
expires_at:
readOnly: true
description: Date and time after which the responsive part no longer will be recommended for default presentation.
type: string
example: "2020-08-22T01:30:00Z"

# ##############################################
# SCHEMA Part
# ##############################################
Part:
type: object
required:
- name
- data
- content_type
properties:
name:
description: Arbritrary file-name that is shown alongside the File in the Kivra GUI
type: string
example: filename.pdf
data:
description: Base64-encoded data
type: string
format: 'Base64-encoded data'
example: REVBREJFRUY=
content_type:
description: The [Iana](http://www.iana.org/assignments/media-types/media-types.xhtml) Media type corresponding to the file, e.g. "application/pdf"
type: string
example: "application/pdf"
responsive_part:
$ref: '#/components/schemas/ResponsivePart'

# ##############################################
# SCHEMA File
# ##############################################
Expand Down