{"openapi":"3.1.0","info":{"title":"Frankfurter over Airnode","version":"2.0.0","description":"Frankfurter's foreign exchange rates, blended from what central banks and other official sources publish rather than taken from any one of them. Latest and historical daily rates, reaching back to 1999 for the currencies that go that far. getCurrencies says which currencies are covered and getProviders which sources are blended, so a caller that needs either asks rather than assuming.\n\nEvery response carries an EIP-191 signature from the Airnode that served it, so the data can be attributed back to its source rather than trusted on reputation."},"servers":[{"url":"https://airnode-frankfurter.fly.dev"}],"paths":{"/":{"get":{"operationId":"getDocumentation","summary":"This document.","description":"Describes the operations this Airnode serves and how to verify what it returns. Free.","responses":{"200":{"description":"An OpenAPI document describing this Airnode.","content":{"application/json":{"schema":{"type":"object"}}}}}},"post":{"operationId":"performRequest","summary":"Perform an operation and attest to the response.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"title":"getRates","description":"Exchange rates from one base currency to many, latest or historical.","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getRates","description":"One record per pair per date, the base against itself included at a rate of 1. Omit every date parameter for the latest rates, give date for a single day, or from and to for a range. Every calendar day in a range answers, weekends and holidays included, since the sources being blended keep different publishing calendars. A range with no quotes returns every currency for every day in it, which is where a call gets large enough to be worth narrowing.","x-airnode-price":"0.01 USD per call","x-airnode-returns":{"date":"string","base":"string","quote":"string","rate":"number","providers":"array"}},"parameters":{"type":"object","additionalProperties":false,"required":[],"properties":{"date":{"description":"A single day. Refused alongside from or to.","type":"string","format":"date"},"from":{"description":"First day of a range.","type":"string","format":"date"},"to":{"description":"Last day of a range. Omitted, the range runs to today.","type":"string","format":"date"},"base":{"description":"The currency every rate is quoted from, as an ISO 4217 code. Omitted, it is EUR.","type":"string","examples":["USD"]},"quotes":{"description":"Currencies to quote into, comma separated. Omitted, every covered currency is returned. Over a range that is every currency for every day, so narrow a range with this.","type":"string","examples":["USD","GBP","JPY"]},"providers":{"description":"Blend only these sources, comma separated by their key. getProviders lists the keys. Omitted, every source that publishes the pair is blended.","type":"string","examples":["ECB","FRED"]},"group":{"description":"Downsample a range to one record per week or per month. Each record is dated the period it starts, which can fall before from. Ignored without a range.","type":"string","enum":["week","month"]},"expand":{"description":"Set to providers to add each source's own observation date and rate to every record, the ones excluded from the blend included and flagged. Costs a recompute per date, so a long range is refused with it.","type":"string"}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string","pattern":"^/"},"maxProperties":20,"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data is then the text of a JSON object of those names in Unicode code point order, each value as the upstream wrote it, instead of the whole body, and the projection is part of the signed request identity. No two names take the same pointer, and no pointer is inside another's value. A pointer the response does not have is a 400."}}},{"title":"getRate","description":"One exchange rate for one currency pair.","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getRate","description":"The blended rate for a single pair, latest or on a date. A pair of the same currency answers 1. This is getRates narrowed to one pair, so reach for it when the pair is already known and for getRates when it is not.","x-airnode-price":"0.01 USD per call","x-airnode-returns":{"date":"string","base":"string","quote":"string","rate":"number","providers":"array"}},"parameters":{"type":"object","additionalProperties":false,"required":["base","quote"],"properties":{"base":{"description":"The currency the rate is quoted from, as an ISO 4217 code.","type":"string","examples":["EUR"]},"quote":{"description":"The currency the rate is quoted into, as an ISO 4217 code.","type":"string","examples":["USD"]},"date":{"description":"The day to price. Omitted, the latest rate.","type":"string","format":"date"},"providers":{"description":"Blend only these sources, comma separated by their key. getProviders lists the keys.","type":"string","examples":["ECB","FRED"]}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string","pattern":"^/"},"maxProperties":20,"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data is then the text of a JSON object of those names in Unicode code point order, each value as the upstream wrote it, instead of the whole body, and the projection is part of the signed request identity. No two names take the same pointer, and no pointer is inside another's value. A pointer the response does not have is a 400."}}},{"title":"getCurrencies","description":"The currencies rates can be asked for.","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getCurrencies","description":"Each with its ISO codes, name, symbol and the span of dates it has rates for. This is what a caller reads to find the code getRates and getRate take.","x-airnode-returns":{"iso_code":"string","iso_numeric":"string | null","name":"string","symbol":"string | null","start_date":"string | null","end_date":"string | null"}},"parameters":{"type":"object","additionalProperties":false,"required":[],"properties":{"scope":{"description":"Include currencies that no longer trade, the pre-euro national ones among them. Omitted, only currently active currencies.","type":"string","const":"all"},"providers":{"description":"Limit to the currencies these sources publish, comma separated by their key.","type":"string","examples":["ECB","FRED"]}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string","pattern":"^/"},"maxProperties":20,"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data is then the text of a JSON object of those names in Unicode code point order, each value as the upstream wrote it, instead of the whole body, and the projection is part of the signed request identity. No two names take the same pointer, and no pointer is inside another's value. A pointer the response does not have is a 400."}}},{"title":"getCurrency","description":"One currency, with the sources publishing it and its peg.","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getCurrency","description":"What getCurrencies returns for the currency, plus which sources publish it and, for a pegged currency, what it is pegged to and by whom. A currency that floats carries no peg at all rather than an empty one.","x-airnode-returns":{"iso_code":"string","iso_numeric":"string | null","name":"string","symbol":"string | null","start_date":"string | null","end_date":"string | null","providers":"array","peg":"object"}},"parameters":{"type":"object","additionalProperties":false,"required":["code"],"properties":{"code":{"description":"The currency, as an ISO 4217 code.","type":"string","examples":["USD"]}}},"responseProjection":{"type":"object","additionalProperties":{"type":"string","pattern":"^/"},"maxProperties":20,"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data is then the text of a JSON object of those names in Unicode code point order, each value as the upstream wrote it, instead of the whole body, and the projection is part of the signed request identity. No two names take the same pointer, and no pointer is inside another's value. A pointer the response does not have is a 400."}}},{"title":"getProviders","description":"The sources the rates are blended from.","type":"object","required":["operation","parameters"],"additionalProperties":false,"properties":{"operation":{"const":"getProviders","description":"Every source with its key, the currencies it publishes, the kind of rate it publishes and how far behind its latest publication is. The keys are what the providers parameter of the other operations takes, and reading how stale a source is here is the only way to tell why a blend moved.","x-airnode-returns":{"key":"string","name":"string","country_code":"string | null","rate_type":"string | null","pivot_currency":"string | null","data_url":"string | null","terms_url":"string | null","start_date":"string | null","end_date":"string | null","publish_cadence":"string | null","publishes_missed":"integer | null","currencies":"array"}},"parameters":{"type":"object","additionalProperties":false,"required":[],"properties":{}},"responseProjection":{"type":"object","additionalProperties":{"type":"string","pattern":"^/"},"maxProperties":20,"description":"Optional. Maps a name of your choosing to an RFC 6901 JSON Pointer into the upstream response, such as {\"price\": \"/ethereum/usd\"}. data is then the text of a JSON object of those names in Unicode code point order, each value as the upstream wrote it, instead of the whole body, and the projection is part of the signed request identity. No two names take the same pointer, and no pointer is inside another's value. A pointer the response does not have is a 400."}}}]}}}},"responses":{"200":{"description":"The upstream response and the attestation over it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attestation"}}}},"400":{"description":"The operation exists but the parameters or the projection are missing, unknown or malformed, a path parameter is empty, . or .. or has such a part between slashes, or the projection names too many fields, an empty or repeated pointer, or a path the response does not have. Also a PAYMENT-SIGNATURE header that does not decode to a payment payload."},"402":{"description":"The operation is paid and no valid payment was supplied. The PAYMENT-REQUIRED header carries the x402 payment requirements to retry against."},"404":{"description":"No such operation."},"502":{"description":"The upstream API could not be reached or refused the request."}}}}},"components":{"schemas":{"Attestation":{"type":"object","required":["airnode","requestHash","timestamp","data","signature"],"properties":{"airnode":{"type":"string","description":"The address whose signature attests to this response."},"requestHash":{"type":"string","description":"Identifies the request this response answers."},"timestamp":{"type":"string","description":"Unix seconds at which the response was signed."},"data":{"type":"string","description":"The upstream response body exactly as received, less a leading byte order mark. With a projection, the text of a JSON object keyed by the names it asked for in Unicode code point order, each value as the upstream wrote it."},"signature":{"type":"string","description":"The attestation itself."}}}}},"x-airnode":{"address":"0x230078619710911032BEF192918203359E7DB22f","version":"0.2.0","attestation":{"scheme":"eip191-secp256k1","digest":"keccak256(abi.encodePacked(bytes32 requestHash, uint256 timestamp, bytes dataBytes))","dataBytes":"UTF-8 of data","requestHash":"keccak256(utf8(JSON.stringify([operation, parameterEntriesSortedByName]))), where an object value is itself entries sorted by name, at any depth, and an array keeps its order. Names are compared by Unicode code point. A request that carries a projection appends its entries, sorted the same way, as a third element","verify":"Recover the signer of the signature over the digest and check it equals the address above."},"passthrough":{"url":"https://airnode-frankfurter.fly.dev/api","routes":{"getRates":{"method":"GET","path":"/rates","parameters":{"date":"query","from":"query","to":"query","base":"query","quotes":"query","providers":"query","group":"query","expand":"query"}},"getRate":{"method":"GET","path":"/rate/{base}/{quote}","parameters":{"base":"path","quote":"path","date":"query","providers":"query"}},"getCurrencies":{"method":"GET","path":"/currencies","parameters":{"scope":"query","providers":"query"}},"getCurrency":{"method":"GET","path":"/currency/{code}","parameters":{"code":"path"}},"getProviders":{"method":"GET","path":"/providers","parameters":{}}},"parameters":{"x-airnode-sign":"false skips the signature: no attestation headers, and the body streamed through with no size cap.","x-airnode-project":"alias:pointer, once per field to keep and at most 20 times, each pointer an RFC 6901 JSON Pointer into the upstream response that starts with /, is not repeated and is not inside another's value. The body becomes an object of those aliases in Unicode code point order, the signature covers it, and a pointer the response does not have is a 400."},"flow":"Send the request the provider documents, method, path, query and body as they are, to url followed by the route's path. The body comes back as the upstream sent it, with the upstream's status, and on success the attestation is in the X-Airnode-Address, X-Airnode-Request-Hash, X-Airnode-Timestamp and X-Airnode-Signature headers, with the operation served in X-Airnode-Operation. Every query parameter starting with x-airnode- is this Airnode's own, listed under parameters, and never reaches the upstream.","requestHash":"keccak256(utf8(JSON.stringify([\"passthrough\", method, path, queryEntries, headerEntries, body]))), where method is upper case, path is what was sent after url as a URL parser resolves it, the query entries are what was sent with the Airnode's own parameters removed, in the order sent, headerEntries are the headers that the routes declare as parameters and the request sent, each name in lower case and sorted by Unicode code point, with the values of a header sent more than once joined with \", \" in the order sent and the result trimmed of spaces and tabs, and body is the request body as sent, or an empty string. A request that carries a projection appends its entries, sorted by name in Unicode code point order, as a seventh element","dataBytes":"UTF-8 of the response body exactly as received, less a leading byte order mark, or of the projected object"},"payment":{"x402Version":2,"scheme":"exact","accepts":[{"network":"eip155:84532","asset":"0xbeA34973e5f67F18F656A2C6070C64326C791808","symbol":"aUSD","decimals":6}],"payTo":"0x1505FD562AC2Fb4307B85BC6c672FfeafB4d57A7","facilitator":"https://facilitator.xpay.sh","prices":{"getRates":"0.01","getRate":"0.01"},"flow":"An unpaid call to a priced operation returns 402 with the x402 payment requirements, base64-encoded, in the PAYMENT-REQUIRED header. Sign an EIP-3009 authorization matching one entry and retry the call with the payment payload, base64-encoded, in the PAYMENT-SIGNATURE header. The settlement receipt comes back in the PAYMENT-RESPONSE header."}}}