├── .github
├── dependabot.yml
├── linters
│ └── .openapirc.yml
└── workflows
│ ├── generated-pr.yml
│ ├── linter.yml
│ └── stale.yml
├── .gitignore
├── .spectral.yaml
├── LICENSE
├── Makefile
├── README.md
├── docs
├── index.html
└── readme.md
└── ipfs-pinning-service.yaml
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
--------------------------------------------------------------------------------
/.github/linters/.openapirc.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | extends: spectral:oas
4 |
--------------------------------------------------------------------------------
/.github/workflows/generated-pr.yml:
--------------------------------------------------------------------------------
1 | name: Close Generated PRs
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | stale:
14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1
15 |
--------------------------------------------------------------------------------
/.github/workflows/linter.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: lint
3 | on:
4 | push:
5 |
6 | jobs:
7 | super-linter:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout Code
11 | uses: actions/checkout@v3
12 | - name: Validate OpenAPI
13 | uses: docker://github/super-linter:v4
14 | env:
15 | VALIDATE_ALL_CODEBASE: true
16 | VALIDATE_OPENAPI: true
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 | spectral-cli:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Checkout Code
22 | uses: actions/checkout@v3
23 | - uses: actions/setup-node@v3
24 | with:
25 | node-version: 'lts/*'
26 | - run: npx @stoplight/spectral-cli lint ./ipfs-pinning-service.yaml
27 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Close Stale Issues
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * *'
6 | workflow_dispatch:
7 |
8 | permissions:
9 | issues: write
10 | pull-requests: write
11 |
12 | jobs:
13 | stale:
14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | super-linter.log
2 |
--------------------------------------------------------------------------------
/.spectral.yaml:
--------------------------------------------------------------------------------
1 | extends: spectral:oas
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: lint-spectral lint-openapi
2 |
3 |
4 | lint-spectral:
5 | npx @stoplight/spectral-cli lint ./ipfs-pinning-service.yaml
6 | lint-openapi:
7 | docker run --rm -e RUN_LOCAL=true -e VALIDATE_OPENAPI=true -v $(shell pwd):/tmp/lint github/super-linter:v4
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pinning Service API Spec
2 |
3 | [](http://protocol.ai)
4 | [](https://ipfs.io/)
5 | [](https://github.com/ipfs/pinning-services-api-spec/actions?query=workflow%3ALint+branch%3Amain)
6 | [](https://github.com/ipfs/specs/#understanding-the-meaning-of-the-spec-badges-and-their-lifecycle)
7 |
8 | > This repository contains the specs for the vendor-agnostic pinning service API for the IPFS ecosystem
9 |
10 | [](#about)
11 |
12 | - [About](#about)
13 | - [Specification](#specification)
14 | - [Code generation](#code-generation) (client/server)
15 | - [Adoption](#adoption)
16 | - [Client libraries](#client-libraries)
17 | - [Server implementations](#server-implementations)
18 | - [Online services](#online-services)
19 | - [Contribute](#contribute)
20 |
21 | ## About
22 |
23 | A **pinning service** is a service that accepts [CIDs](https://github.com/ipld/cid/) from a user in order to host the data associated with them.
24 |
25 | The rationale behind defining a generic pinning service API is to have a baseline functionality and interface that can be provided by pinning services, so that tools can be built on top of a common base of functionality.
26 |
27 | In [this presentation](https://youtu.be/Pcv8Bt4HMVU), IPFS creator Juan Benet discusses current and potential pinning use cases, and how a standardized IPFS pinning API can meet these envisioned needs.
28 |
29 | The API spec in this repo is the first step towards that future.
30 |
31 | ## Specification
32 |
33 | This API is defined as an OpenAPI spec in YAML format:
34 |
35 | * **[ipfs-pinning-service.yaml](./ipfs-pinning-service.yaml)** 
36 |
37 |
38 | ### Documentation
39 |
40 | You can find human-readable API documentation generated from the YAML file here:
41 |
42 | - **[https://ipfs.github.io/pinning-services-api-spec](https://ipfs.github.io/pinning-services-api-spec/)** 
43 |
44 | ### Code generation
45 |
46 | https://openapi-generator.tech allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically, given the OpenAPI spec at [ipfs-pinning-service.yaml](./ipfs-pinning-service.yaml).
47 |
48 | Give it a try before you resort to implementing things from scratch.
49 |
50 | ## Adoption
51 |
52 | Built-in support for pinning services exposing this API is coming to IPFS tooling:
53 | - [go-ipfs](https://github.com/ipfs/go-ipfs)  (since [v0.8.0](https://github.com/ipfs/go-ipfs/releases/v0.8.0): `ipfs pin remote --help`, see how to [work with remote pinning services](https://docs.ipfs.io/how-to/work-with-pinning-services/))
54 | - [js-ipfs-http-client](https://www.npmjs.com/package/ipfs-http-client)  (`ipfs.pin.remote.*` JS APIs)
55 | - [ipfs-cluster](https://cluster.ipfs.io)  ([issue](https://github.com/ipfs/ipfs-cluster/issues/1213))
56 | - [js-ipfs](https://github.com/ipfs/js-ipfs#readme) –  ([js-ipfs/pull/3588](https://github.com/ipfs/js-ipfs/pull/3588))
57 | - [ipfs-webui](https://github.com/ipfs-shipyard/ipfs-webui)  (remote pin support since [v2.12.0](https://github.com/ipfs/ipfs-webui/releases/v2.12.0))
58 | - [ipfs-desktop](https://github.com/ipfs-shipyard/ipfs-desktop)  (>0.20.x)
59 |
60 | ### Client libraries
61 | - [js-pinning-service-http-client](https://github.com/ipfs-shipyard/js-pinning-service-http-client/) 
62 | An IPFS Pinning Service HTTP Client library for JS, used in [compliance test suite](https://github.com/ipfs/pinning-services-api-spec/issues/64).
63 | - [go-pinning-service-http-client](https://github.com/ipfs/go-pinning-service-http-client) 
64 | An IPFS Pinning Service HTTP Client library for Go, used by go-ipfs internally in `ipfs pin remote --help` commands.
65 | - https://openapi-generator.tech/docs/generators#client-generators 
66 | Use [YAML file](./ipfs-pinning-service.yaml) to generate client for your language
67 | - [auspinner](https://github.com/2color/auspinner)
68 | A stateless CLI tool to pin and serve CAR files to IPFS pinning services using HTTP and Bitswap.
69 |
70 | ### Server implementations
71 | - https://github.com/ipfs/ipfs-cluster 
72 | Pinset orchestration for IPFS – [tracking issue](https://github.com/ipfs/ipfs-cluster/issues/1213)
73 | - https://github.com/ipfs-shipyard/js-mock-ipfs-pinning-service 
74 | Implementation of in-memory service for testing purposes
75 | - https://github.com/ipfs-shipyard/rb-pinning-service-api 
76 | A Rails app that implements the IPFS Pinning Service API
77 | - https://openapi-generator.tech/docs/generators#server-generators 
78 | Use [YAML file](./ipfs-pinning-service.yaml) to generate server boilerplate for your language
79 |
80 | ### CI/CD
81 |
82 | - https://github.com/marketplace/actions/ipfs-remote-pinning
83 | IPFS Pinning GitHub Action that adds data to IPFS and pins it to any `ENDPOINT` compatible with Pinning Service API
84 |
85 | ### Online services
86 |
87 | - https://pinata.cloud – ([documentation](https://pinata.cloud/documentation#PinningServicesAPI))
88 | - `ipfs pin remote service add pinata https://api.pinata.cloud/psa YOUR_JWT`
89 | - https://nft.storage
90 | - `ipfs pin remote service add nft-storage https://nft.storage/api YOUR_API_KEY`
91 | - https://filebase.com - ([documentation](https://docs.filebase.com/storage-networks/ipfs/ipfs-pinning) + [IPFS Pin Sync](https://docs.filebase.com/ipfs/ipfs-pin-sync))
92 | - `ipfs pin remote service add filebase https://api.filebase.io/v1/ipfs SECRET-ACCESS-TOKEN`
93 | - `{your project could be here}` – open a PR!
94 |
95 | ### Timeline
96 |
97 | - 2022 Q3
98 | - [IPFS Pin Sync](https://docs.filebase.com/ipfs/ipfs-pin-sync) is announced by Filebase
99 | - 2022 Q1
100 | - [web3.storage](https://web3.storage) API support: https://docs.web3.storage/how-tos/pinning-services-api
101 | - [estuary.tech](https://estuary.tech) API support: https://docs.estuary.tech/pinning-list
102 | - Mock server for local development: https://github.com/ipfs-shipyard/js-mock-ipfs-pinning-service
103 | - WIP official API client for JS: https://github.com/ipfs-shipyard/js-pinning-service-http-client/
104 | - WIP compliance test suite: https://github.com/ipfs/pinning-services-api-spec/issues/64
105 | - WIP ipfs-cluster support ([commit](https://github.com/ipfs/ipfs-cluster/commit/9549e0c86e500a0b15020f6e5d48664d1f3ab37d))
106 | - 2021 Q1
107 | - [go-ipfs 0.8.0](https://github.com/ipfs/go-ipfs/releases/v0.8.0) shipped with built-in client for v1.0.0 of this API
108 | - Pinata announces endpoint compatible with this spec: https://pinata.cloud/documentation#PinningServicesAPI
109 | - ipfs-webui [v2.12.0](https://github.com/ipfs/ipfs-webui/releases/v2.12.0) provides UI based on `pin remote` commands
110 | - Textile is [working on Bucket Pinning API](https://github.com/textileio/textile/discussions/499)
111 | - 2020 Q3
112 | - IPFS GUI WG working on adding support for pinning services into IPFS Desktop/Web UI:
113 | - [Epic: Pinning service integration · Issue #91 · ipfs/ipfs-gui](https://github.com/ipfs/ipfs-gui/issues/91)
114 | - [Analysis of remote pinning services vs the needs of IPFS Web UI](https://docs.google.com/document/d/1f0R7woLtW_YTv9P9IOrUNK6QafgctJ7qTggEUdepD_c/)
115 | - [ipfs/pinning-services-api-specs](https://github.com/ipfs/pinning-services-api-specs) is created as a place for stakeholders to collaborate and finalize the API
116 | - 2020-07-14: Spec in draft status is ready for implementation
117 | - 2020-08: Addressing feedback from early implementers
118 | - 2020-09: End-to-end testing
119 | - 2020 Q2
120 | - Pinning Summit 2020 ([website](https://ipfspinningsummit.com/), [recorded talks](https://www.youtube.com/watch?v=rYD2lfuatJM&list=PLuhRWgmPaHtTvsxuZ9T-tMlu_v0lja6v5))
121 | - 2019 Q2
122 | - Creation of a generic pinning service API proposed in [ipfs/notes/issues/378](https://github.com/ipfs/notes/issues/378)
123 |
124 | ## Contribute
125 |
126 | Suggestions, contributions, and criticisms are welcome! However, please make sure to familiarize yourself deeply with IPFS, the models it adopts, and the principles it follows.
127 |
128 | This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
129 |
130 | ### Spec lifecycle
131 |
132 | We use the following label system to identify the state of aspects of this spec:
133 |
134 | -  — A work-in-progress, possibly to describe an idea before actually committing to a full draft of the spec
135 | -  — A draft that is ready to review, and should be implementable
136 | -  — A spec that has been adopted (implemented) and can be used as a reference to learn how the system works
137 | -  — We consider this spec to close to final; it might be improved, but the system it specifies should not fundamentally change
138 | -  — This spec will not change
139 | -  — This spec is no longer in use
140 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | IPFS Pinning Service API
5 |
6 |
7 |
8 |
9 |
10 |
176 |
177 |
178 |
179 |
196 |
197 |
198 |
--------------------------------------------------------------------------------
/docs/readme.md:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /index.html
3 | ---
4 |
--------------------------------------------------------------------------------
/ipfs-pinning-service.yaml:
--------------------------------------------------------------------------------
1 | openapi: 3.0.3
2 | info:
3 | version: "1.0.0"
4 | title: 'IPFS Pinning Service API'
5 | x-logo:
6 | url: "https://bafybeidehxarrk54mkgyl5yxbgjzqilp6tkaz2or36jhq24n3rdtuven54.ipfs.dweb.link/?filename=ipfs-pinning-service.svg"
7 | contact:
8 | name: IPFS
9 | url: https://github.com/ipfs/pinning-services-api-spec
10 | description: "
11 |
12 |
13 | ## About this spec
14 |
15 | The IPFS Pinning Service API is intended to be an implementation-agnostic API:
16 |
17 | - For use and implementation by pinning service providers
18 |
19 | - For use in client mode by IPFS nodes and GUI-based applications
20 |
21 |
22 | ### Document scope and intended audience
23 |
24 | The intended audience of this document is **IPFS developers** building pinning service clients or servers compatible with this OpenAPI spec.
25 | Your input and feedback are welcome and valuable as we develop this API spec. Please join the design discussion at [github.com/ipfs/pinning-services-api-spec](https://github.com/ipfs/pinning-services-api-spec).
26 |
27 |
28 | **IPFS users** should see the tutorial at [docs.ipfs.io/how-to/work-with-pinning-services](https://docs.ipfs.io/how-to/work-with-pinning-services/) instead.
29 |
30 |
31 | ### Related resources
32 |
33 | The latest version of this spec and additional resources can be found at:
34 |
35 | - Specification: https://github.com/ipfs/pinning-services-api-spec/raw/main/ipfs-pinning-service.yaml
36 |
37 | - Docs: https://ipfs.github.io/pinning-services-api-spec/
38 |
39 | - Clients and services: https://github.com/ipfs/pinning-services-api-spec#adoption
40 |
41 |
42 | # Schemas
43 |
44 | This section describes the most important object types and conventions.
45 |
46 |
47 | A full list of fields and schemas can be found in the `schemas` section of the [YAML file](https://github.com/ipfs/pinning-services-api-spec/blob/master/ipfs-pinning-service.yaml).
48 |
49 |
50 | ## Identifiers
51 |
52 | ### cid
53 |
54 | [Content Identifier (CID)](https://docs.ipfs.io/concepts/content-addressing/) points at the root of a DAG that is pinned recursively.
55 |
56 | ### requestid
57 |
58 | Unique identifier of a pin request.
59 |
60 |
61 | When a pin is created, the service responds with unique `requestid` that can be later used for pin removal. When the same `cid` is pinned again, a different `requestid` is returned to differentiate between those pin requests.
62 |
63 |
64 | Service implementation should use UUID, `hash(accessToken,Pin,PinStatus.created)`, or any other opaque identifier that provides equally strong protection against race conditions.
65 |
66 |
67 | ## Objects
68 |
69 | ### Pin object
70 |
71 |
72 | 
73 |
74 |
75 | The `Pin` object is a representation of a pin request.
76 |
77 |
78 | It includes the `cid` of data to be pinned, as well as optional metadata in `name`, `origins`, and `meta`.
79 | Addresses provided in `origins` list are relevant only during the initial pinning, and don't need to be persisted by the pinning service.
80 |
81 |
82 | ### Pin status response
83 |
84 |
85 | 
86 |
87 |
88 | The `PinStatus` object is a representation of the current state of a pinning operation.
89 |
90 | It includes values from the original `Pin` object, along with the current `status` and globally unique `requestid` of the entire pinning request, which can be used for future status checks and management.
91 | Addresses in the `delegates` array are peers designated by pinning service that will receive the pin data over bitswap (more details in the [Provider hints](#section/Provider-hints) section). Any additional vendor-specific information is returned in optional `info`.
92 |
93 |
94 | # The pin lifecycle
95 |
96 |
97 | 
98 |
99 |
100 | ## Creating a new pin object
101 |
102 | The user sends a `Pin` object to `POST /pins` and receives a `PinStatus` response:
103 |
104 | - `requestid` in `PinStatus` is the identifier of the pin operation, which can can be used for checking status, and removing the pin in the future
105 |
106 | - `status` in `PinStatus` indicates the current state of a pin
107 |
108 |
109 | ## Checking status of in-progress pinning
110 |
111 | `status` (in `PinStatus`) may indicate one of the two pending states: `queued` or `pinning`:
112 |
113 | - `queued` is passive: the pin was added to the queue but the service isn't consuming any resources to retrieve it yet.
114 | - `pinning` is active: the pinning service is trying to retrieve the CIDs by finding providers for all involved CIDs, connect to these providers and download data from them.
115 |
116 | When a new pin object is created it typically starts in a `queued` state. Once the pinning service actively seeks to retrieve the file it changes to `pinning`. `pinning` typically means that the data behind `Pin.cid` was not found on the pinning service and is being fetched from the IPFS network at large, which may take time.
117 |
118 | In either case, the user can periodically check pinning progress via `GET /pins/{requestid}` until pinning is successful, or the user decides to remove the pending pin.
119 |
120 | ## Replacing an existing pin object
121 |
122 | The user can replace an existing pin object via `POST /pins/{requestid}`. This is a shortcut for removing a pin object identified by `requestid` and creating a new one in a single API call that protects against undesired garbage collection of blocks common to both pins. Useful when updating a pin representing a huge dataset where most of blocks did not change. The new pin object `requestid` is returned in the `PinStatus` response. The old pin object is deleted automatically.
123 |
124 |
125 | ## Removing a pin object
126 |
127 | A pin object can be removed via `DELETE /pins/{requestid}`.
128 |
129 |
130 |
131 | # Provider hints
132 |
133 | Provider hints take the form of two [multiaddr](https://docs.ipfs.io/concepts/glossary/#multiaddr) lists: `Pin.origins` and `PinStatus.delegates`
134 |
135 |
136 | ## Pin.origins
137 |
138 | A list of known sources (providers) of the data. Sent by a client in a pin request.
139 | Pinning service will try to connect to them to speed up data transfer.
140 |
141 |
142 | ## PinStatus.delegates
143 |
144 | A list of temporary destination (retrievers) for the data. Returned by pinning service in a response for a pin request.
145 | These peers are provided by a pinning service for the purpose of fetching data about to be pinned.
146 |
147 |
148 | ## Optimizing for speed and connectivity
149 |
150 | Both ends should attempt to preconnect to each other:
151 |
152 | - Delegates should always preconnect to origins
153 |
154 | - Clients who initiate pin request and also have the pinned data in their own local datastore should preconnect to delegates
155 |
156 |
157 | **NOTE:** Connections to multiaddrs in `origins` and `delegates` arrays should
158 | be attempted in best-effort fashion, and dial failure should not fail the
159 | pinning operation. When unable to act on explicit provider hints, DHT and
160 | other discovery methods should be used as a fallback by a pinning service.
161 |
162 |
163 | ## Rationale
164 |
165 | A pinning service will use the DHT and other discovery methods to locate pinned
166 | content; however, it may not be able to retrieve data if the only provider
167 | has no publicly diallable address (e.g. a desktop peer behind a restrictive NAT/firewall).
168 |
169 |
170 | Leveraging provider hints mitigates potential connectivity issues and speeds up the content routing phase.
171 | If a client has the data in their own datastore or already knows of other providers, the transfer will start immediately.
172 |
173 |
174 | The most common scenario is a client putting its own IPFS node's multiaddrs in
175 | `Pin.origins`, and then attempt to connect to every multiaddr returned by a
176 | pinning service in `PinStatus.delegates` to initiate transfer. At the same
177 | time, a pinning service will try to connect to multiaddrs provided by the client
178 | in `Pin.origins`.
179 |
180 |
181 | This ensures data transfer starts immediately (without waiting for provider
182 | discovery over DHT), and mutual direct dial between a client and a service
183 | works around peer routing issues in restrictive network topologies, such as
184 | NATs, firewalls, etc.
185 |
186 |
187 | **NOTE:** All multiaddrs MUST end with `/p2p/{peerID}` and SHOULD be fully
188 | resolved and confirmed to be dialable from the public internet. Avoid sending
189 | addresses from local networks.
190 |
191 |
192 | # Custom metadata
193 |
194 | Pinning services are encouraged to add support for additional features by leveraging the optional `Pin.meta` and `PinStatus.info` fields.
195 | While these attributes can be application- or vendor-specific, we encourage the community at large to leverage these attributes as a sandbox to come up with conventions that could become part of future revisions of this API.
196 |
197 | ## Pin metadata
198 |
199 | String keys and values passed in `Pin.meta` are persisted with the pin object.
200 | This is an opt-in feature: It is OK for a client to omit or ignore these optional attributes, and doing so should not impact the basic pinning functionality.
201 |
202 |
203 | Potential uses:
204 |
205 | - `Pin.meta[app_id]`: Attaching a unique identifier to pins created by an app enables meta-filtering pins per app
206 |
207 | - `Pin.meta[vendor_policy]`: Vendor-specific policy (for example: which region to use, how many copies to keep)
208 |
209 |
210 | ### Filtering based on metadata
211 |
212 | The contents of `Pin.meta` can be used as an advanced search filter for situations where searching by `name` and `cid` is not enough.
213 |
214 |
215 | Metadata key matching rule is `AND`:
216 |
217 | - lookup returns pins that have `meta` with all key-value pairs matching the passed values
218 |
219 | - pin metadata may have more keys, but only ones passed in the query are used for filtering
220 |
221 |
222 | The wire format for the `meta` when used as a query parameter is a [URL-escaped](https://en.wikipedia.org/wiki/Percent-encoding) stringified JSON object.
223 | A lookup example for pins that have a `meta` key-value pair `{\"app_id\":\"UUID\"}` is:
224 |
225 | - `GET /pins?meta=%7B%22app_id%22%3A%22UUID%22%7D`
226 |
227 |
228 |
229 | ## Pin status info
230 |
231 | Additional `PinStatus.info` can be returned by pinning service.
232 |
233 |
234 | Potential uses:
235 |
236 | - `PinStatus.info[status_details]`: more info about the current status (queue
237 | position, percentage of transferred data, summary of where data is stored, etc); when
238 | `PinStatus.status=failed`, it could provide a reason why a pin operation
239 | failed (e.g. lack of funds, DAG too big, etc.)
240 |
241 | - `PinStatus.info[dag_size]`: the size of pinned data, along with DAG overhead
242 |
243 | - `PinStatus.info[raw_size]`: the size of data without DAG overhead (eg. unixfs)
244 |
245 | - `PinStatus.info[pinned_until]`: if vendor supports time-bound pins, this could indicate when the pin will expire
246 |
247 |
248 | # Pagination and filtering
249 |
250 | Pin objects can be listed by executing `GET /pins` with optional parameters:
251 |
252 |
253 | - When no filters are provided, the endpoint will return a small batch of the 10 most
254 | recently created items, from the latest to the oldest.
255 |
256 | - The number of returned items can be adjusted with the `limit` parameter
257 | (implicit default is 10).
258 |
259 | - If the value in `PinResults.count` is bigger than the length of
260 | `PinResults.results`, the client can infer there are more results that can be queried.
261 |
262 | - To read more items, pass the `before` filter with the timestamp from
263 | `PinStatus.created` found in the oldest item in the current batch of results.
264 | Repeat to read all results.
265 |
266 | - Returned results can be fine-tuned by applying optional `after`, `cid`, `name`,
267 | `status`, or `meta` filters.
268 |
269 |
270 | > **Note**: pagination by the `created` timestamp requires each value to be
271 | globally unique. Any future considerations to add support for bulk creation
272 | must account for this.
273 |
274 |
275 | "
276 |
277 | servers:
278 | - url: https://pinning-service.example.com
279 |
280 | tags:
281 | - name: pins
282 |
283 | paths:
284 | /pins:
285 | get:
286 | operationId: getPins
287 | summary: List pin objects
288 | description: List all the pin objects, matching optional filters; when no filter is provided, only successful pins are returned
289 | tags:
290 | - pins
291 | parameters:
292 | - $ref: '#/components/parameters/cid'
293 | - $ref: '#/components/parameters/name'
294 | - $ref: '#/components/parameters/match'
295 | - $ref: '#/components/parameters/status'
296 | - $ref: '#/components/parameters/before'
297 | - $ref: '#/components/parameters/after'
298 | - $ref: '#/components/parameters/limit'
299 | - $ref: '#/components/parameters/meta'
300 | responses:
301 | '200':
302 | description: Successful response (PinResults object)
303 | content:
304 | application/json:
305 | schema:
306 | $ref: '#/components/schemas/PinResults'
307 | '400':
308 | $ref: '#/components/responses/BadRequest'
309 | '401':
310 | $ref: '#/components/responses/Unauthorized'
311 | '404':
312 | $ref: '#/components/responses/NotFound'
313 | '409':
314 | $ref: '#/components/responses/InsufficientFunds'
315 | '4XX':
316 | $ref: '#/components/responses/CustomServiceError'
317 | '5XX':
318 | $ref: '#/components/responses/InternalServerError'
319 | post:
320 | operationId: addPin
321 | summary: Add pin object
322 | description: Add a new pin object for the current access token
323 | tags:
324 | - pins
325 | requestBody:
326 | required: true
327 | content:
328 | application/json:
329 | schema:
330 | $ref: '#/components/schemas/Pin'
331 | responses:
332 | '202':
333 | description: Successful response (PinStatus object)
334 | content:
335 | application/json:
336 | schema:
337 | $ref: '#/components/schemas/PinStatus'
338 | '400':
339 | $ref: '#/components/responses/BadRequest'
340 | '401':
341 | $ref: '#/components/responses/Unauthorized'
342 | '404':
343 | $ref: '#/components/responses/NotFound'
344 | '409':
345 | $ref: '#/components/responses/InsufficientFunds'
346 | '4XX':
347 | $ref: '#/components/responses/CustomServiceError'
348 | '5XX':
349 | $ref: '#/components/responses/InternalServerError'
350 |
351 | /pins/{requestid}:
352 | parameters:
353 | - name: requestid
354 | in: path
355 | required: true
356 | schema:
357 | type: string
358 | get:
359 | operationId: getPinByRequestId
360 | summary: Get pin object
361 | description: Get a pin object and its status
362 | tags:
363 | - pins
364 | responses:
365 | '200':
366 | description: Successful response (PinStatus object)
367 | content:
368 | application/json:
369 | schema:
370 | $ref: '#/components/schemas/PinStatus'
371 | '400':
372 | $ref: '#/components/responses/BadRequest'
373 | '401':
374 | $ref: '#/components/responses/Unauthorized'
375 | '404':
376 | $ref: '#/components/responses/NotFound'
377 | '409':
378 | $ref: '#/components/responses/InsufficientFunds'
379 | '4XX':
380 | $ref: '#/components/responses/CustomServiceError'
381 | '5XX':
382 | $ref: '#/components/responses/InternalServerError'
383 | post:
384 | operationId: replacePinByRequestId
385 | summary: Replace pin object
386 | description: Replace an existing pin object (shortcut for executing remove and add operations in one step to avoid unnecessary garbage collection of blocks present in both recursive pins)
387 | tags:
388 | - pins
389 | requestBody:
390 | required: true
391 | content:
392 | application/json:
393 | schema:
394 | $ref: '#/components/schemas/Pin'
395 | responses:
396 | '202':
397 | description: Successful response (PinStatus object)
398 | content:
399 | application/json:
400 | schema:
401 | $ref: '#/components/schemas/PinStatus'
402 | '400':
403 | $ref: '#/components/responses/BadRequest'
404 | '401':
405 | $ref: '#/components/responses/Unauthorized'
406 | '404':
407 | $ref: '#/components/responses/NotFound'
408 | '409':
409 | $ref: '#/components/responses/InsufficientFunds'
410 | '4XX':
411 | $ref: '#/components/responses/CustomServiceError'
412 | '5XX':
413 | $ref: '#/components/responses/InternalServerError'
414 | delete:
415 | operationId: deletePinByRequestId
416 | summary: Remove pin object
417 | description: Remove a pin object
418 | tags:
419 | - pins
420 | responses:
421 | '202':
422 | description: Successful response (no body, pin removed)
423 | '400':
424 | $ref: '#/components/responses/BadRequest'
425 | '401':
426 | $ref: '#/components/responses/Unauthorized'
427 | '404':
428 | $ref: '#/components/responses/NotFound'
429 | '409':
430 | $ref: '#/components/responses/InsufficientFunds'
431 | '4XX':
432 | $ref: '#/components/responses/CustomServiceError'
433 | '5XX':
434 | $ref: '#/components/responses/InternalServerError'
435 |
436 | components:
437 | schemas:
438 |
439 | PinResults:
440 | description: Response used for listing pin objects matching request
441 | type: object
442 | required:
443 | - count
444 | - results
445 | properties:
446 | count:
447 | description: The total number of pin objects that exist for passed query filters
448 | type: integer
449 | format: int32
450 | minimum: 0
451 | example: 1
452 | results:
453 | description: An array of PinStatus results
454 | type: array
455 | items:
456 | $ref: '#/components/schemas/PinStatus'
457 | uniqueItems: true
458 | minItems: 0
459 | maxItems: 1000
460 |
461 | PinStatus:
462 | description: Pin object with status
463 | type: object
464 | required:
465 | - requestid
466 | - status
467 | - created
468 | - pin
469 | - delegates
470 | properties:
471 | requestid:
472 | description: Globally unique identifier of the pin request; can be used to check the status of ongoing pinning, or pin removal
473 | type: string
474 | example: "UniqueIdOfPinRequest"
475 | status:
476 | $ref: '#/components/schemas/Status'
477 | created:
478 | description: Immutable timestamp indicating when a pin request entered a pinning service; can be used for filtering results and pagination
479 | type: string
480 | format: date-time # RFC 3339, section 5.6
481 | example: "2020-07-27T17:32:28.276Z"
482 | pin:
483 | $ref: '#/components/schemas/Pin'
484 | delegates:
485 | $ref: '#/components/schemas/Delegates'
486 | info:
487 | $ref: '#/components/schemas/StatusInfo'
488 |
489 | Pin:
490 | description: Pin object
491 | type: object
492 | required:
493 | - cid
494 | properties:
495 | cid:
496 | description: Content Identifier (CID) to be pinned recursively
497 | type: string
498 | example: "QmCIDToBePinned"
499 | name:
500 | description: Optional name for pinned data; can be used for lookups later
501 | type: string
502 | maxLength: 255
503 | example: "PreciousData.pdf"
504 | origins:
505 | $ref: '#/components/schemas/Origins'
506 | meta:
507 | $ref: '#/components/schemas/PinMeta'
508 |
509 | Status:
510 | description: Status a pin object can have at a pinning service
511 | type: string
512 | enum:
513 | - queued # pinning operation is waiting in the queue; additional info can be returned in info[status_details]
514 | - pinning # pinning in progress; additional info can be returned in info[status_details]
515 | - pinned # pinned successfully
516 | - failed # pinning service was unable to finish pinning operation; additional info can be found in info[status_details]
517 |
518 | Delegates:
519 | description: List of multiaddrs designated by pinning service that will receive the pin data; see Provider Hints in the docs
520 | type: array
521 | items:
522 | type: string
523 | uniqueItems: true
524 | minItems: 1
525 | maxItems: 20
526 | example: ['/ip4/203.0.113.1/tcp/4001/p2p/QmServicePeerId']
527 |
528 | Origins:
529 | description: Optional list of multiaddrs known to provide the data; see Provider Hints in the docs
530 | type: array
531 | items:
532 | type: string
533 | uniqueItems: true
534 | minItems: 0
535 | maxItems: 20
536 | example: ['/ip4/203.0.113.142/tcp/4001/p2p/QmSourcePeerId', '/ip4/203.0.113.114/udp/4001/quic/p2p/QmSourcePeerId']
537 |
538 | PinMeta:
539 | description: Optional metadata for pin object
540 | type: object
541 | additionalProperties:
542 | type: string
543 | minProperties: 0
544 | maxProperties: 1000
545 | example:
546 | app_id: "99986338-1113-4706-8302-4420da6158aa" # Pin.meta[app_id], useful for filtering pins per app
547 |
548 | StatusInfo:
549 | description: Optional info for PinStatus response
550 | type: object
551 | additionalProperties:
552 | type: string
553 | minProperties: 0
554 | maxProperties: 1000
555 | example:
556 | status_details: "Queue position: 7 of 9" # PinStatus.info[status_details], when status=queued
557 |
558 | TextMatchingStrategy:
559 | description: Alternative text matching strategy
560 | type: string
561 | default: exact
562 | enum:
563 | - exact # full match, case-sensitive (the implicit default)
564 | - iexact # full match, case-insensitive
565 | - partial # partial match, case-sensitive
566 | - ipartial # partial match, case-insensitive
567 |
568 | Failure:
569 | description: Response for a failed request
570 | type: object
571 | required:
572 | - error
573 | properties:
574 | error:
575 | type: object
576 | required:
577 | - reason
578 | properties:
579 | reason:
580 | type: string
581 | description: Mandatory string identifying the type of error
582 | example: "ERROR_CODE_FOR_MACHINES"
583 | details:
584 | type: string
585 | description: Optional, longer description of the error; may include UUID of transaction for support, links to documentation etc
586 | example: "Optional explanation for humans with more details"
587 |
588 | parameters:
589 |
590 | before:
591 | description: Return results created (queued) before provided timestamp
592 | name: before
593 | in: query
594 | required: false
595 | schema:
596 | type: string
597 | format: date-time # RFC 3339, section 5.6
598 | example: "2020-07-27T17:32:28.276Z"
599 |
600 | after:
601 | description: Return results created (queued) after provided timestamp
602 | name: after
603 | in: query
604 | required: false
605 | schema:
606 | type: string
607 | format: date-time # RFC 3339, section 5.6
608 | example: "2020-07-27T17:32:28.276Z"
609 |
610 | limit:
611 | description: Max records to return
612 | name: limit
613 | in: query
614 | required: false
615 | schema:
616 | type: integer
617 | format: int32
618 | minimum: 1
619 | maximum: 1000
620 | default: 10
621 |
622 | cid:
623 | description: Return pin objects responsible for pinning the specified CID(s); be aware that using longer hash functions introduces further constraints on the number of CIDs that will fit under the limit of 2000 characters per URL in browser contexts
624 | name: cid
625 | in: query
626 | required: false
627 | schema:
628 | type: array
629 | items:
630 | type: string
631 | uniqueItems: true
632 | minItems: 1
633 | maxItems: 10
634 | style: form # ?cid=Qm1,Qm2,bafy3
635 | explode: false
636 | example: ["Qm1","Qm2","bafy3"]
637 |
638 | name:
639 | description: Return pin objects with specified name (by default a case-sensitive, exact match)
640 | name: name
641 | in: query
642 | required: false
643 | schema:
644 | type: string
645 | maxLength: 255
646 | example: "PreciousData.pdf"
647 |
648 | match:
649 | description: Customize the text matching strategy applied when the name filter is present; exact (the default) is a case-sensitive exact match, partial matches anywhere in the name, iexact and ipartial are case-insensitive versions of the exact and partial strategies
650 | name: match
651 | in: query
652 | required: false
653 | schema:
654 | $ref: '#/components/schemas/TextMatchingStrategy'
655 | example: exact
656 |
657 | status:
658 | description: Return pin objects for pins with the specified status (when missing, service should default to pinned only)
659 | name: status
660 | in: query
661 | required: false
662 | schema:
663 | type: array
664 | items:
665 | $ref: '#/components/schemas/Status'
666 | uniqueItems: true
667 | minItems: 1
668 | style: form # ?status=queued,pinning
669 | explode: false
670 | example: ["queued","pinning"]
671 |
672 | meta:
673 | description: Return pin objects that match specified metadata keys passed as a string representation of a JSON object; when implementing a client library, make sure the parameter is URL-encoded to ensure safe transport
674 | name: meta
675 | in: query
676 | required: false
677 | content:
678 | application/json: # ?meta={"foo":"bar"}
679 | schema:
680 | $ref: '#/components/schemas/PinMeta'
681 |
682 | responses:
683 |
684 | BadRequest:
685 | description: Error response (Bad request)
686 | content:
687 | application/json:
688 | schema:
689 | $ref: '#/components/schemas/Failure'
690 | examples:
691 | BadRequestExample:
692 | $ref: '#/components/examples/BadRequestExample'
693 |
694 | Unauthorized:
695 | description: Error response (Unauthorized; access token is missing or invalid)
696 | content:
697 | application/json:
698 | schema:
699 | $ref: '#/components/schemas/Failure'
700 | examples:
701 | UnauthorizedExample:
702 | $ref: '#/components/examples/UnauthorizedExample'
703 |
704 | NotFound:
705 | description: Error response (The specified resource was not found)
706 | content:
707 | application/json:
708 | schema:
709 | $ref: '#/components/schemas/Failure'
710 | examples:
711 | NotFoundExample:
712 | $ref: '#/components/examples/NotFoundExample'
713 |
714 | InsufficientFunds:
715 | description: Error response (Insufficient funds)
716 | content:
717 | application/json:
718 | schema:
719 | $ref: '#/components/schemas/Failure'
720 | examples:
721 | InsufficientFundsExample:
722 | $ref: '#/components/examples/InsufficientFundsExample'
723 |
724 | CustomServiceError:
725 | description: Error response (Custom service error)
726 | content:
727 | application/json:
728 | schema:
729 | $ref: '#/components/schemas/Failure'
730 | examples:
731 | CustomServiceErrorExample:
732 | $ref: '#/components/examples/CustomServiceErrorExample'
733 |
734 | InternalServerError:
735 | description: Error response (Unexpected internal server error)
736 | content:
737 | application/json:
738 | schema:
739 | $ref: '#/components/schemas/Failure'
740 | examples:
741 | InternalServerErrorExample:
742 | $ref: '#/components/examples/InternalServerErrorExample'
743 |
744 | examples:
745 |
746 | BadRequestExample:
747 | value:
748 | error:
749 | reason: "BAD_REQUEST"
750 | details: "Explanation for humans with more details"
751 | summary: A sample response to a bad request; reason will differ
752 |
753 | UnauthorizedExample:
754 | value:
755 | error:
756 | reason: "UNAUTHORIZED"
757 | details: "Access token is missing or invalid"
758 | summary: Response to an unauthorized request
759 |
760 | NotFoundExample:
761 | value:
762 | error:
763 | reason: "NOT_FOUND"
764 | details: "The specified resource was not found"
765 | summary: Response to a request for a resource that does not exist
766 |
767 | InsufficientFundsExample:
768 | value:
769 | error:
770 | reason: "INSUFFICIENT_FUNDS"
771 | details: "Unable to process request due to the lack of funds"
772 | summary: Response when access token run out of funds
773 |
774 | CustomServiceErrorExample:
775 | value:
776 | error:
777 | reason: "CUSTOM_ERROR_CODE_FOR_MACHINES"
778 | details: "Optional explanation for humans with more details"
779 | summary: Response when a custom error occured
780 |
781 | InternalServerErrorExample:
782 | value:
783 | error:
784 | reason: "INTERNAL_SERVER_ERROR"
785 | details: "Explanation for humans with more details"
786 | summary: Response when unexpected error occured
787 |
788 | securitySchemes:
789 | accessToken:
790 | description: "
791 | An opaque token is required to be sent with each request in the HTTP header:
792 |
793 | - `Authorization: Bearer `
794 |
795 |
796 | The `access-token` should be generated per device, and the user should have the ability to revoke each token separately.
797 | "
798 | type: http
799 | scheme: bearer
800 | security:
801 | - accessToken: []
802 |
--------------------------------------------------------------------------------