If you are experiencing an unexpected or inconsistent behavior when using the App Store Server Library, review the following resources to ensure that your implementation workflow didn’t cause the issue:
Simplifying your implementation by using the App Store Server Library
Explore App Store server APIs for In-App Purchase
Meet the App Store Server Library
If you are unable to resolve your issue using the above resources, file a GitHub issue. Alternatively, if you wish to provide specific requests, transactions, or other private information for review, submit a Feedback Assistant report with the following information:
The bundleId or appAppleId of your app
The date and time your issue occurred
The library language(s)
The version of the library
The environment (i.e., Production, Sandbox, or Xcode)
The GitHub issue for this report if available
The endpoint(s) reproducing your issue
The HTTP body and headers of the endpoint raw request
The HTTP body and headers of the endpoint response
To submit the report, perform these steps:
Log into Feedback Assistant.
Click on the Compose icon to create a new report.
Select the Developer Tools & Resources topic.
In the sheet that appears:
Enter a title for your report.
Select “App Store Server Library” from the “Which area are you seeing an issue with?” pop-up menu.
Select “Incorrect/Unexpected Behavior” from the “What type of feedback are you reporting?” pop-up menu.
Enter a description of your issue and how to reproduce it.
Add the information gathered above to the sheet.
Submit your report.
After filing your report, please respond in your existing Developer Forums post with the Feedback Assistant ID. Use your Feedback Assistant ID to check for updates or resolutions. For more information, see Understanding feedback status.
App Store Server Library
RSS for tagApp Store Server Library is the library for the App Store Server API and App Store Server Notifications. It provides an API client, a JWS signed data verifier, a utility to extract a transaction id from a receipt, and a promotional offer signature.
Posts under App Store Server Library tag
28 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
To receive server notifications from the App Store, follow the instructions in Enabling App Store Server Notifications. If your server doesn’t receive any notifications, check your server logs for any incoming web request issues, and
confirm that your server supports the Transport Layer Security (TLS) 1.2 protocol or later. If you implement version 2 of App Store Server Notifications, call the Get Notification History endpoint. If there is an issue sending a notification, the endpoint returns the error the App Store received from your server.
If your issue persists, submit a Feedback Assistant report with the following information:
The bundleId or appAppleId of your app
The date and time your issue occurred
The raw HTTP body of your notification
The affected transactionId(s) if applicable
The version of App Store Server Notifications (i.e., Version 1 or Version 2)
The environment (i.e., Production or Sandbox)
To submit the report, perform these steps:
Log into Feedback Assistant.
Click on the Compose icon to create a new report.
Select the Developer Tools & Resources topic.
In the sheet that appears:
Enter a title for your report.
Select “App Store Server Notifications” from the “Which area are you seeing an issue with?” pop-up menu.
Select “Incorrect/Unexpected Behavior” from the “What type of feedback are you reporting?” pop-up menu.
Enter a description of your issue.
Add the information gathered above to the sheet.
Submit your report.
After filing your report, please respond in your existing Developer Forums post with the Feedback Assistant ID. Use your Feedback Assistant ID to check for updates or resolutions. For more information, see Understanding feedback status.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
App Store Server Notifications
App Store Server Library
I'm successfully using Apple subscriptions in my app, but I'm encountering SKErrorCodeDomain error 18 when trying to apply a subscription offer.
I want apply offer code first time only for subscription. Below are details of what i set in appstore and what i have tested.
Subscription Offer Details
Offer Type: For the first month
Customer Eligibility: New, Existing, and Expired Subscribers
Code Status: Active
Offer Code Creation Steps:
App Store Connect → App → Subscription → Select Subscription Product → Offer Codes → Add → Add Custom Codes
Signature Generation for Promotional Offers
I'm following Apple's documentation to generate a signature:
https://developer.apple.com/documentation/storekit/generating-a-signature-for-promotional-offers
I’ve constructed the payload as instructed:
appBundleId + '\u2063' + keyIdentifier + '\u2063' + productIdentifier + '\u2063' + offerIdentifier + '\u2063' + appAccountToken + '\u2063' + nonce + '\u2063' + timestamp
Keys and Identifiers
keyIdentifier, issuerId, and .p8 file are obtained from:
App Store Connect → Users and Access → Integrations → In-App Purchase
Test user created under:
App Store Connect → Users and Access → Sandbox → Test Accounts
Logged in with this account on the iPhone
What I’ve Tried
Verified all values used in the payload are correct
Tried both seconds and milliseconds for the timestamp (as per documentation, it should be in milliseconds)
Tried setting appAccountToken to:
a valid UUID
an empty string
not setting it at all
Used Apple’s sample code to generate a signature: https://developer.apple.com/documentation/storekit/generating-a-promotional-offer-signature-on-the-server
Verified the generated signature locally, and it validated successfully: https://developer.apple.com/documentation/storekit/generating-a-signature-for-promotional-offers#Validate-locally-and-encode-the-signature
Apple’s sample code to generate a signature
Downloaded from
const express = require('express');
const router = express.Router();
const crypto = require('crypto');
const ECKey = require('ec-key');
const secp256k1 = require('secp256k1');
const uuidv4 = require('uuid/v4');
const KeyEncoder = require('key-encoder');
const keyEncoder = new KeyEncoder('secp256k1');
const fs = require('fs');
function getKeyID() {
return "KEYIDXXXXX";
}
router.post('/offer', function(req, res) {
const appBundleID = req.body.appBundleID;
const productIdentifier = req.body.productIdentifier;
const subscriptionOfferID = req.body.offerID;
const applicationUsername = req.body.applicationUsername;
const nonce = uuidv4();
const currentDate = new Date();
const timestamp = currentDate.getTime();
const keyID = getKeyID();
const payload = appBundleID + '\u2063' +
keyID + '\u2063' +
productIdentifier + '\u2063' +
subscriptionOfferID + '\u2063' +
applicationUsername + '\u2063'+
nonce + '\u2063' +
timestamp;
// Get the PEM-formatted private key string associated with the Key ID.
// const keyString = getKeyStringForID(keyID);
// Read the .p8 file
const keyString = fs.readFileSync('./SubscriptionKey_47J5826J8W.p8', 'utf8');
// Create an Elliptic Curve Digital Signature Algorithm (ECDSA) object using the private key.
const key = new ECKey(keyString, 'pem');
// Set up the cryptographic format used to sign the key with the SHA-256 hashing algorithm.
const cryptoSign = key.createSign('SHA256');
// Add the payload string to sign.
cryptoSign.update(payload);
/*
The Node.js crypto library creates a DER-formatted binary value signature,
and then base-64 encodes it to create the string that you will use in StoreKit.
*/
const signature = cryptoSign.sign('base64');
/*
Check that the signature passes verification by using the ec-key library.
The verification process is similar to creating the signature, except it uses 'createVerify'
instead of 'createSign', and after updating it with the payload, it uses `verify` to pass in
the signature and encoding, instead of `sign` to get the signature.
This step is not required, but it's useful to check when implementing your signature code.
This helps debug issues with signing before sending transactions to Apple.
If verification succeeds, the next recommended testing step is attempting a purchase
in the Sandbox environment.
*/
const verificationResult = key.createVerify('SHA256').update(payload).verify(signature, 'base64');
console.log("Verification result: " + verificationResult)
// Send the response.
res.setHeader('Content-Type', 'application/json');
res.json({ 'keyID': keyID, 'nonce': nonce, 'timestamp': timestamp, 'signature': signature });
});
module.exports = router;
Postman request and response
Request URL: http://192.168.1.141:3004/offer
Request JSON: {
"appBundleID":"com.app.bundleid",
"productIdentifier":"subscription.product.id",
"offerID":"OFFERCODE1",
"applicationUsername":"01234b43791ea309a1c3003412bcdaaa09d39a615c379cc246f5f479760629a1"
}
Response JSON: {
"keyID": "KEYIDXXXXX",
"nonce": "f98f2cda-c7a6-492f-9f92-e24a6122c0c9",
"timestamp": 1753510571664,
"signature": "MEYCIQCnA8UGWhTiCF+F6S55Zl6hpjnm7SC3aAgvmTBmQDnsAgIhAP6xIeRuREyxxx69Ve/qjnONq7pF1cK8TDn82fyePcqz"
}
Xcode Code
func buy(_ product: SKProduct) {
let discountOffer = SKPaymentDiscount(
identifier: "OFFERCODE1",
keyIdentifier: "KEYIDXXXXX",
nonce: UUID(uuidString: "f98f2cda-c7a6-492f-9f92-e24a6122c0c9")!,
signature: "MEYCIQCnA8UGWhTiCF+F6S55Zl6hpjnm7SC3aAgvmTBmQDnsAgIhAP6xIeRuREyxxx69Ve/qjnONq7pF1cK8TDn82fyePcqz",
timestamp: 1753510571664)
let payment = SKMutablePayment(product: product)
payment.applicationUsername = "01234b43791ea309a1c3003412bcdaaa09d39a615c379cc246f5f479760629a1"
payment.paymentDiscount = discountOffer
SKPaymentQueue.default().add(payment)
}
Issue
Even following instructions to the documentation and attempting various combinations, the offer keeps failing with SKErrorCodeDomain error 18.
Has anyone else experienced this? Any suggestions as to what may be amiss or how it can be corrected?
Topic:
App & System Services
SubTopic:
Apple Pay
Tags:
Subscriptions
In-App Purchase
Apple Pay
App Store Server Library
The minimum support for the project is iOS 15.2, and the subscription function is implemented using StoreKit2.
Problem: The redemption was successful within the Appstore, but the redemption item cannot be detected through code within the app.(The subscription function has been implemented and tested)
Here is my code, I am not sure if it is due to storeKit2 (as seen elsewhere) or if there is a problem with the testing method. If there is a correct testing method for the promoCode redemption scenario, please let me know.
for await verificationResult in Transaction.currentEntitlements {
switch verificationResult {
case .verified(let transaction):
if transaction.revocationDate != nil {
print("unsubscribe:\(transaction)")
break
}
if transaction.offerType == .code,let code = transaction.offerID {
print("Have promoCode")
print("promoCode: \(code)")
let dateF = DateFormatter()
dateF.dateFormat = "yyyy.MM.dd HH.mm.ss"
if let expireDate = transaction.expirationDate {
print("endTime:\(dateF.string(from: expireDate))")
}
}
//.consumable,.nonConsumable,.autoRenewable,.nonRenewable
if transaction.productType == .autoRenewable {
print("Have subscription:\(transaction)")
let dateF = DateFormatter()
dateF.dateFormat = "yyyy.MM.dd HH.mm.ss"
if let expireDate = transaction.expirationDate {
print("endTime:\(dateF.string(from: expireDate))")
}
}else{
print("\(transaction)")
}
case .unverified(let unverifiedTransaction, let verificationError):
print("checkProduct:error")
}
}
Hey everyone,
We're looking for the best way to handle App Store Server Notifications in our development setup and would appreciate some guidance.
Our Setup:
We use a single App Store Connect account for development, which supports multiple environments (e.g., staging1, staging2). Our production app lives in a separate account, so that's not an issue.
The Challenge:
We have only one configurable sandbox notification URL. This makes it difficult to route notifications to the correct development server (staging1 vs. staging2 vs developments) when a sandbox event occurs.
We're considering using a proxy server to catch all notifications and then forward them to the appropriate environment. However, we're not sure how to determine the correct destination.
Our Questions:
What's the recommended approach for managing a single sandbox notification URL across multiple development environments?
If a proxy is the best method, which parameter in the responseBodyV2 payload should we use to route the notification? How can we differentiate between our various dev environments?
Is it possible to add custom properties to the App Store Server Notification V2 body to facilitate routing?
Any advice or best practices you've implemented would be greatly appreciated.
Hey everyone,
We're looking for the best way to handle App Store Server Notifications in our development setup and would appreciate some guidance.
Our Setup:
We use a single App Store Connect account for development, which supports multiple environments (e.g., staging1, staging2). Our production app lives in a separate account, so that's not an issue.
The Challenge:
We have only one configurable sandbox notification URL. This makes it difficult to route notifications to the correct development server (staging1 vs. staging2 vs development) when a sandbox event occurs.
We're considering using a proxy server to catch all notifications and then forward them to the appropriate environment. However, we're not sure how to determine the correct destination.
Our Questions:
What's the recommended approach for managing a single sandbox notification URL across multiple development environments?
If a proxy is the best method, which parameter in the responseBodyV2 payload should we use to route the notification? How can we differentiate between our various dev environments?
Is it possible to add custom properties to the App Store Server Notification V2 body to facilitate routing?
Any advice or best practices you've implemented would be greatly appreciated.
Thanks!
Topic:
Community
SubTopic:
Apple Developers
Tags:
Subscriptions
App Store Server Notifications
App Store Server API
App Store Server Library
Hello Apple Support Team,
We are using auto-renew plans in our app
We have set the webhook URL in our App Store Connect account to get the Store server notification to get the auto renew data for an user
The issue is when a user purchases any auto-renew plan at the auto-renew time, we are getting null data from the Apple side.
We have printed the log's data to check what data are coming from the Apple webhook. we have attached our logs data please check it and let me know what can we do to resolve this
Topic:
App & System Services
SubTopic:
Notifications
Tags:
Subscriptions
App Store Server Notifications
App Store Server Library
We offer a 3-day free trial, and our paywall clearly states that users will be charged after the trial ends.
However, some users request refunds after the charge - even after fully using our app for days or even weeks. In some cases, refunds are approved despite the users having consumed our AI processing services for up to a month.
Since our app relies on backend AI processing, each user session incurs a real cost. To prevent losses, we utilize RevenueCat’s CONSUMPTION_REQUEST system and have set our refundPreference to: "2. You prefer that Apple declines the refund".
Until recently, Apple typically respected this preference, and 90% of refund requests were declined as intended.
However, starting about a week ago, we observed a sudden reversal: Apple is now approving around 90% of refund requests, despite our refund preference. As a result, we are operating at a loss and have had to halt both our marketing campaigns and our 3-day free trial.
We’re trying to understand whether this shift is due to a change in Apple’s refund policy, or if we need to handle CONSUMPTION_REQUEST differently on our end.
Has anyone else experienced similar changes? Any insights would be greatly appreciated.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit
App Store Server Notifications
App Store Server Library
Hello,
We've integrated App Store Server Notifications V2 in our system. We are heavily relying on the ONE_TIME_CHARGE notification type to handle Consumable purchases, but this notification type is available only for Sandbox. And this is for a while now - starting with June 10th 2024 ( https://developer.apple.com/documentation/appstoreservernotifications/app-store-server-notifications-changelog#June-10-2024 ).
Can you please provide a timeline for when the ONE_TIME_CHARGE notification type will be available in Production ?
Thank you!
When using the AppStoreServerAPI to retrieve user purchase information or decode a Payload,
The following responses occur multiple times throughout the day. If there is no error, the process is completed successfully.
HTTP response code: 502 for URL
What is the cause of this error occurring repeatedly? Is there still a problem with Apple's server status?
The following APIs are used to acquire and decode user purchase information.
AppStoreServerAPIClient.getAllSubscriptionStatuses
SignedDataVerifier.verifyAndDecodeTransaction
SignedDataVerifier.verifyAndDecodeRenewalInfo
Topic:
App Store Distribution & Marketing
SubTopic:
General
Tags:
App Store Server API
App Store Server Library
When the user's purchase information was retrieved from Japan using the AppStoreServerAPI,
I was able to obtain it with the status ‘1: The auto-renewable subscription is active.’,
However, the expiry date in the Payload was the previous day. How can this be the case?
Example: when the purchase information was retrieved on 10/02/2025 at 21:00:00, the expiry date for status ‘1’ was ‘2025/02/09’.
Topic:
App Store Distribution & Marketing
SubTopic:
General
Tags:
App Store Server API
App Store Server Library
Please let me ask you about the following status code obtained from the AppStoreServerAPI.
3: The auto-renewable subscription is in a Billing retry period.
4: The auto-renewable subscription is in a Billing Grace Period.
5: The auto-renewable subscription is revoked. the App Store refunded the transaction or revoked it from Family Sharing.
For these status codes 3 to 5, would this mean that the service has not been purchased? And will they change to a status of ‘2: The auto-renewable subscription is expired’ after a certain period of time without the payment issue being resolved?
Topic:
App Store Distribution & Marketing
SubTopic:
General
Tags:
App Store Server API
App Store Server Library
Hi, all!
I have an AppStore Server-side question. User sends up an AppReceipt that I am validating. What's the best way to tell the receipt belongs to said user? I want to make sure that the source of the AppReceipt was actually the original purchaser of the item. Is fetching Transaction + AppAccountToken the only way? AppAccountToken can only be utilized if the original purchase used it, and it is associated with the user's data. Is there another way?
Hi apple team,
I'm using Apple Root Certificates from https://www.apple.com/certificateauthority/ for communicating with App Store Server Library for receipt validation API.
Apple Computer, Inc Root certificate from the website is Not Valid After: Monday, 10 February 2025 at 01:18:14 Central European Standard Time.
When we can expect update of this certificate.
Thank you
For the subscription API, we’re using the Get All Subscription Statuses API to replace the deprecated verifyReceipt method. To determine if a user has canceled their subscription, we’re using the expirationIntent key from JWSRenewalInfo data. However, we’ve noticed that we sometimes receive this key and other times not.
We’re also facing an issue with the offertype key. We use this key to determine if a user is currently in the introductory offer, the promotion offer, or neither. To obtain this key, we’re using JWSTransaction, but we occasionally receive it and other times not.
Note: These issues are being tested in the sandbox environment.
Thank you.
Inquire the types of notifications that can occur in a SANDBOX environment
Hello, WWDC 2024 is trying to conduct a test to receive notifications related to ONE_TIME_CHARGE, CONNSUMPTION_REQUEST, CONMSUMPTION_INFO, REFUND, and REFUND_DECLINED as described in the example of purchasing consumables, but as a result of the continuous search, I found that it is difficult to occur except for
ONE_TIME_CHARGE.
So, in order to verify only the business logic as shown below, we are testing only the business logic without actually calling the API after purchasing the test and saving the signaled Payload that we received in response to ONE_TIME_CHARGE. Can we actually request a refund for the test purchase and receive the corresponding notification and actually send the response?
public void handleSignedNotification(String signedNotification) throws Exception {
ResponseBodyV2DecodedPayload payload = signedDataVerifier.verifyAndDecodeNotification(signedNotification);
NotificationTypeV2 type = payload.getNotificationType();
//For Apple Server Notification, only ONE_TIME_CHARGE notifications are enabled in the test environment, so for testing, change them as below to test whether they are running business logic
type = NotificationTypeV2.REFUND;
log.info("Apple NotificationType : {}", type);
switch (type) {
case CONSUMPTION_REQUEST:
handleConsumptionRequest(payload);
break;
case REFUND:
handleRefund(payload);
break;
case REFUND_DECLINED:
handleRefundDeclined(payload);
break;
// For other necessary notifications, just take a log
default:
log.info("Unhandled notification: {}", type);
}
}
Regarding the call of 'CONSUMPTION_INFO', which is the response of 'CONSUMPTION_REQUEST'
Is there a value that WWDC 2024 must include when sending CONMSUMPTION_INFO, which is the response to CONNSUMPTION_REQUEST described in the refund example? I'm going to call the API with only sample provision and consumption like the sample code you introduced in the video.
I was told to submit my refund preference within 12 hours, but can I submit it as UNDECLARED at first and use the method to express my intention? When I receive the notification, I will save it in the DB and save it in the administrator page of the service so that the administrator can choose.
2-1. Some of the materials I looked for are told that Apple can proceed with the refund even 12 hours ago, and to express your opinion as soon as I receive the notification, but I wonder if this is correct.
If you get a notification as below, you should write whether you used it or not by referring to the consumption information. I think the customer said to check whether the data was provided when applying for a refund. Should I take it out of decodedTransaction, check the value, and just call it NO_PREFERENCE? I'd appreciate it if you could give me some advice.
Below is a part of the code I implemented.
private void handleConsumptionRequest(ResponseBodyV2DecodedPayload notification) throws Exception {
// 1. transaction ID get
String signedTransactionInfo = notification.getData().getSignedTransactionInfo();
JWSTransactionDecodedPayload decodedTransaction = signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo);
String transactionId = decodedTransaction.getTransactionId();
// 2. Extract the relevant transaction (The following example is an in-app payment and will be accumulated in two types of DBs, stored in one of the two)
Sample sample = sampleService.findByAppleTransactionId(transactionId);
Example example = exampleService.findByAppleTransactionId(transactionId);
Boolean canRefund = false;
// 3. Check consumption information
if (sample != null) {
canRefund = checkSampleStatusForApplePurchaseRefund(sample);
} else if (example != null) {
canRefund = checkExampleStatusForApplePurchaseRefund(example);
}
// 4. Create Refund Preferences
RefundPreference refundPreference = determineRefundPreference(canRefund);
// 5. Creating a ConsumptionRequest Object
ConsumptionRequest request = new ConsumptionRequest()
.refundPreference(refundPreference)
.sampleContentProvided(true);
log.info("forTest~ canRefund: {}", canRefund);
log.info("forTest~ sample: {}", sample.toString());
log.info("forTest~ example: {}", example.toString());
log.info("forTest~ refundPreference: {}", refundPreference);
log.info("forTest~ request: {}", request);
// 6. Transfer to App Store (annotated with dummy requests that only confirm current business requests are going right)
// appStoreServerAPIClient.sendConsumptionData(transactionId, request);
}
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
In-App Purchase
App Store Server Notifications
App Store Server Library
Greetings!
I was reviewing the documentation for the Apple Store Server Notification v1 APIs and saw the deprecation notice. We are currently unable to migrate to v2 in the near future. With that in mind, I want to know if there were any plans to shut off v1, and if so, was there any timeline we could expect? Thank you in advance.
Reference: https://developer.apple.com/documentation/appstoreservernotifications/app-store-server-notifications-version-1
I get this error:
Verification failed with status INVALID_APP_IDENTIFIER
we use this method:
payload = signed_data_verifier.verify_and_decode_notification(notification_data)
I use Production environment (this is automatically set in our server) and use my device (connected to Mac) to test the purchase. When trying to purchase I get the Sandbox environment in the Widget.
This works when manually setting the environment to Sandbox before calling the function. The time it works because it does not check the apple app id (because of the environment).
Now my question is: Which is the apple app id? Is it the App Id "Apple-ID" in the app store connect? Or is it the TeamId+BundleId?
Could it be that my function doesnt work because the Payment was done in Sandbox and the environment is Production?
Topic:
Developer Tools & Services
SubTopic:
Xcode Cloud
Tags:
App Store Server API
App Store Server Library
This documentation describes what kind of data we should be sending to Apple server, once we are receiving CONSUMPTION_REQUEST
https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest
But, it doesn't describe what kind of data we are receiving, when we are receiving CONSUMPTION_REQUEST?
May I know, is such a document available?
Thank you.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
App Store Server Notifications
App Store Server API
App Store Server Library
Hello. I am trying to give an update to my app but it again and again gets rejected due to the ATT Prompt. Before this late week I gave the update and it got live without any issue. Now I done some minor changes in the App.
Apple Response.
The app uses the AppTrackingTransparency framework, but we are unable to locate the App Tracking Transparency permission request when reviewed on iPadOS 18.2.
Next Steps
Explain where we can find the App Tracking Transparency permission request in the app. The request should appear before any data is collected that could be used to track the user.
If App Tracking Transparency is implemented but the permission request is not appearing on devices running the latest operating system, review the available documentation and confirm App Tracking Transparency has been correctly implemented.
If your app does not track users, update your app privacy information in App Store Connect to not declare tracking. You must have the Account Holder or Admin role to update app privacy information.
My Response:
Hello Apple Team
Thank you for your feedback.
We have tested the app on iPadOS 18.2, also on iPhone 18.1 and the App Tracking Transparency dialogue is appearing as expected on the main home screen when the user enters the app. To help demonstrate this, we’ve attached a video showing the ATT prompt in action.
If there is still an issue with the dialogue or if it needs to be placed in a different position, we kindly request your guidance on what needs to be adjusted. Please let us know the details so we can address it promptly.
Thank you for your support
"I uploaded a video with images showcasing the ATT prompt but now again they rejected the update with the exact same reply. Which I am thinking it may be a bot reply.
Now what to do how to solve it?
Topic:
Privacy & Security
SubTopic:
General
Tags:
App Tracking Transparency
AdSupport
App Store Server Library